深入理解synchronized

加锁原理

  synchronized (a){} 锁住的就是()里面的对象,多个线程对同一个对象操作时,就会形成互斥效果,如果是操作两个不同的对象,那么就不会受synchronized影响。

public class SynchronizedDemo {
    public static void main(String[] args) {
        SynchronizedDemo s = new SynchronizedDemo();
        Integer a = 1;
        Integer b = 2;

        new Thread(()->{
            s.sync(a);
        }).start();
        new Thread(()->{
            s.sync(b);
            //s.sync(a);
        }).start();
    }

    public void sync(Integer a){
        synchronized (a){
            System.out.println("线程:"+Thread.currentThread().getName()+" 获取到变量"+a);
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在jdk中,synchronized同步是基于Monitor对象实现的,它里面主要有两个指令:
  monitorenter: 插入到同步代码块的开始位置
  monitorexit: 插入到同步代码块结束的位置
它们对应着JMM模型8大原子操作的lock与unlock,lock获取锁后把对象加载到工作内存,数据操作完之后重新赋值到主内存,最后unlock解锁。JVM需要保证每一个monitorenter都有一个monitorexit与之对应。为了保证在方法异常时,monitorenter和monitorexit指令也能正常配对执行,编译器会自动产生一个异常处理器,它的目的就是用来执行 异常的monitorexit指令。通过monitor里的加锁计数器可以实现可重入的加锁。 Monitor(监视器锁)是依赖操作系统的Mutex Lock(互斥锁)实现的,需要向内核申请资源,此时cpu将由用户态转换为内核态,它是一个低性能重量级锁。

synchronized优化

  jdk1.6之后就对这个synchronized锁进行了各种优化,是基于 jvm 通过 cas 设置头信息来做的。如适应性自旋锁、轻量级锁和偏向锁,并默认开启偏向锁。从 无锁—>偏向锁—>轻量级锁—>重量级锁 ,锁升级的这个过程是不可逆的。被加锁的对象 jvm中为它定义了一种对应的数据结构,通过判断数据结构的对象头就知道目前是什么锁状态。例如通过倒数第三个bit的值 0/1 就知道目前是无锁还是偏向锁了。

三种锁的区别

  偏向锁:仅有一个线程进入临界区(主要用于不存在锁竞争,而是一个线程多次获得锁时,为的使线程获取锁使用最小的代价(因为只需要修改获取锁的线程id就好了))
  轻量级锁:多个线程交替进入临界区(当其他线程尝试竞争偏向锁时,会升级为轻量锁)
  重量级锁:多个线程同时进入临界区

锁的升级过程

1. 无锁:此时还没有线程获取所得资源

  

 2. 获取偏向锁:第一个线程获取到锁就会将前面的23个bit位修改为自己线程的id,将无锁升级为偏向锁。以后该线程进入或退出同步块只需要看对象头的MarkWord的值就好了

   

 3. 升级轻量锁:偏向锁采用了一种等到竞争出现才释放锁的机制。此时另一个线程尝试获取锁,发现锁里的线程id并不是自己的,等之前线程执行完同步块后就会释放锁,将对象头重的Mark Word替换为指向锁记录的指针,然后其升级为轻量锁。

  

4. 若刚才将对象头重的Mark Word替换为指向锁记录的指针失败,则会自旋(循环等待)来获取锁,此时若有另一个线程同时竞争,锁会升级为重量级锁。

   

 

posted @ 2021-07-12 23:11  吴磊的  阅读(135)  评论(0编辑  收藏  举报
//生成目录索引列表