并发编程之对象锁 — synchronized

2020-03-31  本文已影响0人  凌晨的咸鱼
并发的前提:多线程操作共享变量

对象锁,字如其名,就是给java对象加的锁,包括new出来的实例对象以及class类对象等,必须要有对象才能加锁,java中的每个对象都会有自己的一把锁,常用场景为给普通方法加锁,给静态方法加锁,同步代码块加锁。

1:给普通方法加锁 — 粗粒度,加锁范围为整个方法,范围大,加锁对象为调用此方法的对象

    int a = 1;
    public synchronized int getPrice() {
        a++;
        return a;
    }

线程排队执行,获取当前对象锁的线程执行完此方法,其他线程才能逐一排队进入。JDK源码StringBuffer实现线程安全的策略就是在方法上面加锁。

2:给静态方法加锁 — 粗粒度,加锁范围为整个方法,范围大,加锁对象为调用此静态方法的类对象即class对象。JDK源码中ConcurrentHashMap实现线程安全的策略就是同步代码块细粒度加锁保证并发

    static int a = 1;
    public static synchronized int getPrice() {
        a++;
        return a;
    }

线程排队执行,获取当前class对象锁的线程执行完此方法,其他线程才能逐一排队进入,静态方法是由类直接调用,所以此处加锁的对象是类对象

3:同步代码块加锁 — 细粒度,加锁范围为方法里面的同步代码块那部分,范围小,加锁对象为调用此方法的对象

    int a = 1;
    public synchronized int getPrice() {
        a++;
        a++;
        synchronized (this) {
            a++;
        }
        return a;
    }

线程异步进入方法,在到达synchronized (this){a++;}同步代码块的时候,第一个进入的线程开始执行,其他线程排队等待,加锁对象为调用此方法的对象
synchronized对象锁使用的前提,多线程操作的必须是同一个对象,比如spring默认是单例对象,多线程操作的都是一个实例,所以在方法上加锁是生效的,但是如果使用@Scope("prototype")使spring变为多例对象,在方法上使用synchronized是没有用的。所以,我们可以去理解,为什么分布式环境下synchronized会失效?如何去解决这个问题?

synchronized底层实现 — 每个对象都有一个监视器锁monitor

1:当执行同步代码块时,反编译代码会发现指令中包含monitorenter进入指令和monitorexit退出指令,线程执行monitorenter指令时,monitor的锁计数器初始数量由0加1,代表此线程获取监视器锁即获取该对象锁,其他线程进入则阻塞,该线程执行monitorexit指令时,monitor的锁计数器数量减去1,直到该monitor的数量变为0之后,其他线程才能进入。备注:同一个线程再次进入该对象不需要再次执行monitorenter,直接计数器+1,说明该锁具有重入性

2:当执行同步方法时,常量池中会多一个ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的,当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

上一篇下一篇

猜你喜欢

热点阅读