
作者|杨遥
了解jdk1.6对锁优化之前我们先简单了解一下重量级锁,什么是重量级锁呢,看一下以下代码
public void autoIncrement() {synchronized (this) {total++;}}
JDK1.6之前synchronized还是重量级锁,也可以叫悲观锁或者互斥锁。为什么叫重量级锁呢,分析一下多线程竞争的资源的情况。

我们知道每次只有一个线程能进入同步代码块,在JDK1.6之前其实是对当前对象内部有一个monitor对象进行加锁。如果线程一此时获取到锁了,其他线程则放到等待队列里面。这种原始的实现方式有性能问题,如进入等待队列里面的线程则会有,线程阻塞,上线文切换,操作系统线程调度,用户态内核态切换等。所以JDK1.6之前的锁也叫重量级锁。
JDK1.6之后对synchronized做了以下锁优化。
无锁-->偏向锁-->轻量级锁-->重量级锁
无锁:当前方法没有线程竞争
偏向锁:当前方法有多处同步代码块,且没有发生线程竞争
轻量级锁:也叫自旋锁,乐观锁,CAS操作
重量级锁:线程等待,排队
了解锁升级之前我们必须先了解一个JAVA对象的组成。一个对象的组成分为三个部分。
对象头:包含markword,指向类指针(jdk8也可以叫元数据指针),数组长度(如果当前不是数组,则没有此部分)
实例数据:JAVA对象数据
对齐填充字节:JAVA对象总体呈现大小总是8bit的倍数,方便操作系统寻址
着重关注对象头里面的markword,其实锁升级的过程,都是通过对对象头里面markword的位数进行控制的,markword组成结构如下图。

我们关注一下锁标志位和是否偏向锁。锁升级的过程如下
无锁:是否偏向锁0,锁标志位01
锁竞争非常小,甚至可能没有锁竞争。
public void autoIncrement() {synchronized (this) {total++;}
偏向锁:是否偏向锁1,锁标志位01
此时锁竞争非常小的情况下通过记录对象里面的线程id,第一处释放锁以后,第二次加锁判断加锁的id和记录的线程id是否一样,一样的话直接执行,则是开销非常小,简单记录一下线程id即可
public void autoIncrement() {synchronized (this) {total++;}synchronized (this) {total++;}}
轻量级锁:锁标志为00
由偏向锁产生了一定量锁竞争,此时锁升级为轻量级锁。通过自旋和比较的过程来执行代码块。
重量级锁:锁标志10
此时大量线程来加锁,通过轻量级锁已经有大量的CPU空转,此时非常耗CPU,甚至cpu打满。此时还不如把线程放入到等待队列,至少不会大量消耗CPU,所以此时才会升级到重量级锁。
总结:
无锁:代码块没有锁竞争,此时是无锁。
无锁升级偏向锁:代码块锁竞争小或者同一个线程多次加锁,此时还不需要升级为轻量级锁,通过记录线程id就能搞定。
偏向锁升级轻量级锁:产生了一定锁竞争,此时竞争不是特别大。只需要通过自选和cas的比较操作就能完成。
轻量级锁升级重量级锁:此时大量线程产线锁竞争,是的轻量级锁对cpu产生空转,所以此时将等待加锁的线程放入队列




