Java并发——Synchronized
Synchronized是jvm提供支持的锁,和Lock有很多区别。
针对Synchronized,jvm支持不同层次的实现。按竞争烈度来说,Synchronized会有偏向锁,轻量级锁,重量级锁等3种类型。
针对对象而言,在对象头上的Mark word上会存储锁信息(包括:偏向线程ID、偏向时间戳,是否偏向锁等),对象的hashcode,分代信息等。
针对线程来说,其会维护一个monitor集合,每一个对象都会与获取该对象锁的线程的monitor建立关联。
- 偏向锁
当线程第一次进入同步块时,如果对象头尚未记录偏向锁信息,此时会在对象头的Mark word中登记偏向锁。但是,在线程离开同步块时,这个偏向锁不会主动释放,仍然存留在对象头中。后续线程再次进入同步块时,仅需要从对象头的Mark Word中提取出来判断是否当前线程的偏向锁即可。如果是当前线程,则继续执行,不做任何操作。如果不是当前线程,则判断对象头中记录的线程是否存活,是否还在使用此对象的锁。如果记录的线程死亡或者不再使用,jvm会将该对象恢复至无锁状态。如果还在使用,则升级成轻量级锁。
偏向锁解决的问题是,基本无竞争场景下,降低锁的获取释放成本。
- 轻量级锁
当偏向锁出现线程竞争时,锁会升级成轻量级锁。在竞争轻量级锁时,未获取到锁的线程会自旋若干次数。在自旋期间获取到锁,则获取锁成功,否则自旋超时,锁会继续升级为重量级锁。当然,如果自旋期间,有新的线程参与竞争,也会直接升级为重量级锁。
轻量级锁解决的问题是,在非常低烈度的竞争场景,锁的占用时间也非常短暂时,线程通过一定的自旋次数可以获取到锁。即自旋的成本<<线程挂起&唤醒的成本时,通过线程的自旋可以降低成本。
- 重量级锁
在竞争激烈的场合,线程无法通过少量的自旋来获取到锁,直接挂起排队等待效率更高。
故而,针对Synchronized而言,锁会分为4个级别,无锁,偏向锁,轻量级锁,重量级锁。按竞争程度逐步升级。不同级别的锁实现的方式互有差异,偏向锁仅在对象头的Mark Word上记录,仅一次cas操作即可。轻量级锁依赖线程栈,因为存在竞争的缘故,需要多次cas自旋操作才可能获取到锁。重量级锁则直接挂起线程,不会有自旋等动作的产生。
Synchronized与Lock相比,在竞争不激烈场景下,锁膨胀的这个过程,Lock也可以通过代码进行模拟。但是Synchronized不支持超时,不支持公平非公平,不支持乐观锁等条件,适用面相比会稍窄一些。
Synchronized支持主动唤醒,依赖Object的wait,notify,notifyAll。线程在执行sleep时,不会主动释放锁信息,但是在执行wait时,会释放获取到的锁信息。故在执行wait,notify,notifyAll时,需要通过Synchronized的同步代码块包装后再执行。
- 锁消除
jvm中有一个锁消除的参数配置,在编译期间,如果发现加锁的代码不会有并发可能性,jvm会主动做锁消除,避免无意义的锁。
-XX:+DoEscapeAnalysis -XX:+EliminateLocks
其中+DoEscapeAnalysis表示开启逃逸分析,+EliminateLocks表示锁消除。
参考资料: