前言
synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的,尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchronized已经变得原来越强大了,synchronized效率很低,因为底层操作依赖于操作系统,操作系统切换线程要从用户态切换到内核态,花费很多时间。Java SE1.6为了减少获得锁和释放锁带来的性能消耗引入了偏向锁和轻量级锁
下面解析一下synchronized锁升级过程和原理,希望可以帮助到大家!
什么是锁升级
锁一共有四种状态,级别由低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。这几个状态会随着竞争情况逐渐升级,但是锁可以升级不能降级。一个锁对象刚创建的时候,没有线程调用它,此时处于无锁状态。
对象头
了解锁之前,要先了解对象头,因为锁的实现是依赖对象头中的Mark Word的。根据锁的不同状态,Mark Word的变化如下:
其中无锁状态的时候,锁标志位是01,是否是偏向锁是0
偏向锁
偏向锁的含义
大多数情况下,锁不仅不存在多线程竞争,而是总由同一个线程多次获得,为了让线程获得锁的代价更低引入了偏向锁。使用偏向锁就表明这个锁对象只被一个线程调用,可以减少上下文切换。
一个线程获得了偏向锁,再次访问锁对象的时候不需要进行加锁解锁的操作,只需要判断Mark Word里面的Thread ID是否是自己。
偏向锁的实现
当一个线程访问同步块获取锁的时候,先看锁的标志位是不是01(无锁或者偏向锁),在看偏向锁是不是1。是偏向锁就判断Thread ID是不是指向自己,是就进入同步代码块,不是就用CAS替换对象头;无锁状态直接CAS替换对象头。此外,CAS替换对象头(已偏向)时,先将对象变成无锁状态,然后偏向自己
- 什么时候CAS替换对象头成功?什么时候失败?
偏向锁的释放不是主动的,持有偏向锁的线程执行完同步代码后不会主动释放锁,要等待其他线程来竞争才会释放锁。如果线程A持有偏向锁,但是已经执行完了同步代码,此时线程B来获取偏向锁,CAS就会成功。当线程A并未执行完同步代码,会出现锁竞争,CAS失败,偏向锁升级为轻量级锁。
升级为轻量级锁后锁标志位00,原持有偏向锁的线程持有轻量级锁。
轻量级锁
在多线程竞争不激烈的前提下使用轻量级锁,可以减少重量级锁对线程的阻塞带来的开销。其他线程在竞争锁的时候只需要稍微等待(自旋),就可以获取锁。但是自旋次数有限,超过自旋次数限制就会升级为重量级锁。实现
执行同步代码块的时候,JVM会先在当前线程的栈帧中创建存储锁记录的空间(Lock Record),并将对象头的Mard Word复制到锁记录中(Displaced Mark Word),加锁时使用CAS将对象头的Mark Word替换为指向锁记录的指针,解锁时使用原子的CAS操作将Displaced Mark Word替换回对象头。
在获取锁失败时,表示出现锁竞争,该线程尝试使用自旋来获取锁。
当自旋超过次数限制或者一个线程持有锁,一个线程在自旋,又来额外的线程尝试获取锁,此时轻量级锁会膨胀成重量级锁
锁升级过程
- 偏向锁:一个线程进入临界区
- 轻量级锁:读个线程交替进入临界区
- 重量级锁:多个线程同时进入临界区
锁升级过程: