参考书籍:Java并发编程的艺术
synchronized锁信息存在Make Word 存储对象是hashCode
java 1.6后 为了synchronized减少获取锁和释放锁所带来的性能的消耗,而引入 偏向锁,轻量级锁
一 偏向锁:
当一个锁总是同一个线程获得,为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步代码快方法时,会在对象头和栈帧中的锁信息里存储锁偏向的线程ID,当下次进入退出同步代码块时,只需去对象头的Makr Word判断一下是否有锁信息的偏向锁指向该线程ID
偏向锁的撤销:
当有另一个线程来竞争锁的时候,就不能再使用偏向锁了,要膨胀为轻量级锁。
竞争线程尝试CAS更新对象头失败,会等待到全局安全点(此时不会执行任何代码)撤销偏向锁。
二 轻量级锁:
a线程获得锁,会在a线程的栈帧里创建lock record(锁记录变量),让lock record的指针指向锁对象的对象头中的Makr Word.再让Makr Word 指向lock record.这就是获取了锁,并且会复制一份Makr Word到lock record里。
为什么要拷贝mark word?其实很简单,原因是为了不想在lock与unlock这种底层操作上再加同步。释放锁的时候会让拷贝的displaced mark word 和 mark word
--->轻量级锁,b线程在锁竞争时,发现锁已经被a线程占用,则b线程不进入内核态,让b线程自旋,执行空循环,等待a线程释放锁。如果,完成自旋策略还是发现a线程没有释放锁,或者让c线程占用了。则b线程试图将轻量级锁升级为重量级锁。
a线程解锁后,我们发现,JVM同样使用了CAS来验证mark word在持有锁到释放锁之间,有无被其他线程访问。
如果其他线程在持有锁这段时间里,尝试获取过锁,则可能自身被挂起,而mark word的重量级锁指针也会被相应修改。
释放锁线程视角:所以由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的mark word,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对mark word做了修改,两者比对发现不一致,则切换到重量锁。
因为重量级锁被修改了,所有displaced mark word和原来的mark word不一样了。
如果此时b线程还在自旋状态,a线程释放了锁,b线程自旋完成后,可以获取到锁,那就不会向重量锁升级
三 自旋锁:
补充一点可能有一些刚入门的小伙伴还不知道自旋锁是什么,这里先知道个概念,下次再来从源码分析:
摘录一个博客文章:
阻塞操作由操作系统完成(在Linxu下通过pthread_mutex_lock函数)
线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能。----轻量级锁时,竞争锁时,不轻易阻塞当前线程。
缓解上述问题的办法便是自旋。
那synchronized实现何时使用了自旋锁?答案是在线程进入ContentionList时,也即第一步操作前。线程在进入等待队列时首先进行自旋尝试获得锁,如果不成功再进入等待队列。