本文是学习《深入理解JAVA虚拟机》的第6篇笔记
乐观锁、悲观锁做技术基本都听过。JAVA中锁的实现,用的最多的是synchronized,这是重量级锁,为提高性能,做了很多优化,今天就说说这个加锁的过程。
之前的文章写对象的内存布局时,说过对象头,锁的实现跟这个有很大的关系。不太了解这块的可以先熟悉下。《对象内存布局》。
线程的阻塞、挂起与唤醒都需要切换至内核态,这几个操作都是很消耗内存的。synchronized的实现并不是一开始就上重量级锁,它有一个锁升级的过程,这里先从偏向锁说起。
// Bit-format of an object header (most significant first, big endian layout below):
//
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
//
// unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
// JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
// narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
// unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
偏向锁
64位的虚拟机,对象头是64bit。最后两位是锁状态,倒数第3位,是偏向锁锁状态,1表示加了偏向锁,0代表没有。
当被锁的对象,第一次被线程获取时,虚拟机将的对象的锁标志位设置成01,将此线程ID,通过CAS操作,记录到对象头中,如果成功,偏向锁的标记位就是1。之后该线程每次进入这个锁相关的同步块时,虚拟机不会进行任何别的操作(Locking, Unlocking,对象头的Update等等)
如果此时另外一个线程请求这个锁,撤消偏向锁,升级为轻量级锁。
轻量级锁
轻量级锁的标记位是00,线程栈中将对象头记住记录下来,就是Lock Record空间。哪个线程把自己的Lock Record指针写到被锁对象的对象头中,哪个线程就拥有这个锁。
重量级锁
如果此时别的线程访问被锁的对象,那就开始自旋。竞争加剧,也就是自旋的线程多了,轻量级锁发生锁膨胀,升级为重量级锁。也就是说由用户态切换到内核态。锁标记位是10,没拿到锁的线程阻塞等待。
加锁,指的是锁定对象
锁升级的过程
JDK较早的版本 :OS的资源 互斥量 用户态 -> 内核态的转换 重量级 效率比较低
现代版本进行了优化:无锁 - 偏向锁 -轻量级锁(自旋锁)-重量级锁