synchronized的锁升级和锁膨胀

本文将将讲解java中synchronized从偏向锁逐步走到轻量级锁、自旋锁再到重量级锁的过程,以及java8中的锁降级优化。

偏向锁

偏向第一个拿到锁的线程。

即第一个拿到锁的线程,锁会在对象头Mark Word中通过CAS记录线程ID,该线程以后每次拿锁时都不需要进行CAS(指轻量级锁)。

如果该线程正在执行同步代码块时有其他线程在竞争(指其他线程尝试CAS让Mark Work设置自己的线程ID),会被升级为轻量级锁。

如果其他线程发现Mark Word里记的不是自己,且发现原持有偏向锁的线程已经执行完同步代码块,会尝试CAS把Mark Word中的改为自己的线程ID。

轻量级锁

轻量级锁就是通过CAS进行加锁的。

JVM会给线程的栈帧中创建一个叫锁记录Lock Record的空间,把对象头Mark Word复制到该空间里(Displaced Mark Word),并通过CAS尝试把原对象头Mark Word中锁记录指针指向该锁记录。如果成功,表示线程拿到了锁。如果失败,则进行自选(自旋锁),自旋超过一定次数时升级为重量级锁,这时该线程会被内核挂起。

自旋锁

轻量级锁膨胀为重量级锁前,线程在执行monitorenter指令进入等待队列时,会通过自旋去尝试获得锁。

如果自旋超过一定次数时还未拿到锁,就会进入阻塞状态,等待内核来调度。此时会发生内核态与用户态之间的上下文切换,所以会影响性能(引入自旋锁就是为了减少这个开销)。

因为后面的线程也进行自选尝试获取锁,所以这对于已被阻塞的那些线程来说,会不公平

重量级锁

重量级锁就是通过内核来操作线程。因为频繁出现内核态与用户态的切换,会严重影响性能。

升级为重量级锁时会在堆中创建monitor对象,并将Mark Work指向该monitor对象。monitor中有cxq(ContentionList),EntryList,WaitSet,owner

synchronized的锁升级和锁膨胀_第1张图片

锁降级

Hotspot在1.8开始有了锁降级。在STW期间JVM进入安全点时如果发现有闲置的monitor(重量级锁对象),就会进行锁降级。

synchronized的锁升级和锁膨胀_第2张图片

为什么锁信息存放在对象头里

死磕Sunchronized底层实现--概论中:

因为在Java中任意对象都可以用作锁,因此必定要有一个映射关系,存储该对象以及其对应的锁信息(比如当前哪个线程持有锁,哪些线程在等待)。一种很直观的方法是,用一个全局map,来存储这个映射关系,但这样会有一些问题:需要对map做线程安全保障,不同的sunchronized之间会互相影响,性能差;另外当同步对象较多时,该map可能会占用比较多的内存。

所以最好的办法是将这个映射关系存储在对象头中,因为对象头本身也有一些hashcode、GC相关的数据,所以如果能将锁信息与这些信息共存在对象头中就好了。

也就是说,如果用一个全局map来存对象的锁信息,还需要对该map做线程安全处理,不同的锁之间会有影响,所以直接存到对象头。

你可能感兴趣的:(java,java关键字,synchronized)