前言
在对synchronized的轻量级锁、重量级锁介绍中其实已经对该流程有所讲解,不过不够明朗
本章旨在让大家能够了解,jdk1.6之后synchronized升级的完整流程
1、无锁
无锁状态肯定是最好理解的了,比如说下面这一段代码:
public void test(){
a = b + c;
}
无论是线程A还是线程B来访问都不会有任何锁,平平无奇。
2、无锁 -> 偏向锁
假如我们为这段代码加上了synchronized
public void test(){
synchronized (object) {
a = b + c;
}
}
此时只有线程A来访问这段代码,此时object对象头中的MarkWord还是自己的,于是线程A很轻松就把object中的MarkWord通过CAS进行对换:
对换成功之后,此时object中的MarkWord变成了线程A Lock record的MarkWord。
但是要知道,synchronized是可以重入的,假如线程A又要访问这段代码,JVM又执行了一遍上述流程。一次两次。。。一百次,每次都要进行CAS,但是每次只有线程A使用。
于是对此JVM做了一个小小的优化:如果在重入到一定阈值之后仍然没有任何线程抢占行为发生,JVM就会省略CAS这个操作,以后只要不发生竞争,这个对象就归该线程所有。
3、偏向锁 -> 轻量级锁
同样是之前那段代码,假如这时候线程B也参和进来,但是此时临界资源object是被线程A持有,于是这时候线程B没办法进入临界区,怎么办?
3.1 自旋优化
直接阻塞或者进行上下文切换是十分耗费性能的做法,因为涉及到用户态和内核态的切换,JVM很明显不会这么笨,在这里做了另一个优化:
如果线程B发现临界资源被占用,不会立即放弃CPU使用权,而是在这期间进行自旋尝试对object进行CAS。
因为一般来说,临界资源是不会被占用很久的,做这个优化的意思就是说,反正闲着也是闲着,说不定等一会别人就用完把锁还回来了呢?
3.2 偏向锁撤销
假如这时候线程A使用完object进行归还之后,线程B进行临界资源的抢占。此时无论线程B抢占成功与否,JVM在发现抢占行为这个动作之后,会把object上的偏向锁标志位清除,取而代之的是升级为轻量级锁。
4、轻量级锁 -> 重量级锁
升级为轻量级锁之后,假如此时object被线程B持有,线程A再来尝试获取object时,同样会进行自旋尝试。
但是这个自旋不会一直持续下去,当JVM发现线程B自旋一定次数或者还有线程C来插足时,这时候就会申请monitor对象。
而这个monitor对象才是真正的重量级锁。具体可以参考synchronized的原理和轻量级、重量级以及偏向锁
结尾
synchronized的性能在这些优化下已经很优秀,但是面临一些场景还是无法灵活应付。
对此J.U.C家族下提供的大量并发工具类就可以派上用场了。