Java锁Synchronized之偏向锁

标签:
javasynchronized偏向锁 |
分类: Java进阶类 |
2.3.1
偏向锁
本质上偏向锁就是为了消除CAS,降低Cache一致性流量。
Hotspot的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。
当一个线程访问同步块并获取锁时,会在对象头和线程栈帧中的锁记录里存储锁偏向的线程ID(通过最开始的一次CAS),以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下Mark Word中偏向锁的标识是否设置成1(看看当前是否还处于偏向锁的层次,因为锁会升级的),如果设置了,则当前仍处于偏向锁层次只是还没有线程此刻占有锁,尝试使用CAS将对象头的偏向锁指向当前线程(释放锁时,会将对像头中纪录线程id的这个位置置空,以便其他线程获取该偏向锁);如果没有设置,表示当前可能已经升级到轻量锁甚至重量锁了,则使用CAS竞争锁。
如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。(偏向锁只能在单线程下起作用)
偏向锁的撤销:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。下图中的线程1演示了偏向锁初始化的流程,线程2演示了偏向锁撤销的流程。
http://s4/mw690/003wfAbNzy6MSO08bIvf3&690
通过下图可以更直观的理解偏向锁:
http://s15/mw690/003wfAbNzy6MSNXmzBI0e&690
这张图,省略了轻量级锁相关的几处步骤,将关注点更多地聚焦在偏向锁的状态变化上。
偏向模式和非偏向模式,在下面的mark word表中,主要体现在thread ID字段是否为空,以及tag bits是否为01。
http://s12/mw690/003wfAbNzy6MSNUyibVfb&690
挂起持有偏向锁的线程,这步操作类似GC的pause,但不同之处是,它只挂起持有偏向锁的线程(非当前线程,以当前去尝试获取锁的线程视角)。
在抢占模式的橙色区域说明中有提到,指向当前堆栈中最近的一个lock record(在轻量级锁原理一文有讲到,lock record是进入锁前会在stack上创建的一份内存空间)。
这里提到的最近的一个lock record,其实就是当前锁所在的stack frame上分配的lock record。
偏向锁,是在线程持有该锁的过程中,只要有其他线程此刻来竞争锁,便会挂起当前持有锁的线程切换到轻量锁状态。
偏向锁也会带来额外开销
在JDK6中,偏向锁是默认启用的。它提高了单线程访问同步资源的性能。
但试想一下,如果你的同步资源或代码一直都是多线程访问的,那么消除偏向锁这一步骤对你来说就是多余的。事实上,消除偏向锁的开销还是蛮大的。
所以在你非常熟悉自己的代码前提下,大可禁用偏向锁。
关闭偏向锁:偏向锁在Java 6和Java
7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延迟 -XX:BiasedLockingStartupDela