前言,我们讲了synchronized的作用和基础的原理,但是我们会发现,如果按照synchronized最开始的设计,他的效率是十分低下的,会显得十分鸡肋,所以在java1.6之后,对synchronized的加锁流程进行了优化,也就是我们熟知的锁升级。
原理
synchronized经过优化后,大致可以分为四个锁阶段
无锁
当没有线程访问锁对象的时候,它会处于无锁的状态,它的对象头的指针不会做任何标记,具体对象头的结构等等,还是得结合jvm的知识,这边就不做多赘述了。
偏向锁
当有了一个线程访问的时候,则锁会升级成偏向锁的,会将对象头内的指针指向当当前的线程id,此时这个锁对象是不会释放的,而是由这个线程持续的持有,所以当当前线程重复执行的时候,不需要重新获取锁,也不需要重新竞争,此时的性能是最好的。
轻量锁
当另一个线程竞争这个锁是,偏向锁状态则立即结束,转为轻量锁。因为是轻量级锁,所以是采用cas的方式进行加锁,加锁的流程也比较简单,就是将jvm会在当前线程的栈帧中,创建一个锁记录的空间,用于记录锁对象的markword拷贝值,然后通过cas的方式,将锁对象的markword值,替换为当前线程锁空间的地址。如果成功了,则该线程就拥有了这个锁。解锁的流程也是类似,也是通过cas的方式,尝试将锁空间的值,写回到锁对象中,如果成功则释放锁,如果失败,则表示有别的线程在竞争锁对象,则释放锁的同时,唤醒处于忙等状态的线程。
重量锁
重量锁则不过对赘述,当前竞争的线程超过了两个,则锁对象会升级为重量锁,此时线程如果获取不到锁,是属于挂起状态,而不是忙等。锁的控制权也全权交由操作系统控制。
总结
总的来说synchronized的加锁在jdk6后主要有无锁->偏向锁->轻量锁->重量锁,四个状态,其中偏向锁是效率最高的,重量锁是效率最低的,轻量锁虽然是通过自旋的方式,让线程进入忙等,不是直接由操作系统进行挂起,但长时间的自旋操作是非常消耗资源的,所以jvm在线程竞争时,会设置自旋的次数,规定的次数内,线程未获取到锁,则会升级到重量级锁。