synchronized你以为你真的懂?synchronized在汇编语言上如何实现?

855 阅读6分钟

前言

对于Java开发者来说synchronized关键字肯定不陌生,对它的用法我们可能已经能信手扭来了,但是我们真的对它深入了解吗?如果深入到汇编级别你能说上来吗?

synchronized的作用于用法

多线程并发中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,随着JDK1.6对Synchronized进行了各种优化,有些情况它就并不那么重了。先来回顾下Synchronized的作用及用法

Synchronized的作用:

(1)确保线程互斥的访问同步代码

(2)保证共享变量的修改能够及时可见

(3)有效解决重排序问题

Synchronized用法:

(1)修饰普通方法,锁是当前实例对象

public synchronized void add(){System.out.println("普通方法同步");}
synchronized你以为你真的懂?synchronized在汇编语言上如何实现

字节码指令

(2)修饰静态方法,锁是当前类的Class对象

public static synchronized void put(){System.out.println("静态方法同步");}
synchronized你以为你真的懂?synchronized在汇编语言上如何实现

字节码指令

(3)修饰代码块,锁是Synchronized括号里面配置的对象

public void set(){synchronized(this){System.out.println("代码块同步");}}
synchronized你以为你真的懂?synchronized在汇编语言上如何实现

字节码指令

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态。

关于方法的同步:可以看到相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符,JVM就是根据该标示符来实现方法的同步的,当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

关于同步块的同步:线程执行monitorenter指令时尝试获取monitor的所有权,执行monitorexit其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权

Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“偏向锁”和“轻量级锁”

java对象头

在介绍“轻量级锁”之前我们先来了解下对象头,对象头分为两部分信息,第一部分用于存储自身的运行时数据,如哈希码,GC分代年龄,官方称它为Mark Word,他是实现轻量级锁和偏向锁的关键。另外一部分用于执行方法区对象类型元素的指针,如果是数组的话,还有一个额外的部分用于存储数组的长度,synchronized用的锁时存在Java对象头里面。

偏向锁

加锁: 如果一个线程获得了锁,那么锁就进入了偏向模式,当这个线程再次请求该锁时,无限做任何同步操作。这样就节省了大量有关锁申请的操作,从而提高程序性能。因此对于几乎没有锁竞争的场合,偏向锁有比较好的优化效果,因为连续多次极有可能是同一线程请求相同的锁。而对于锁竞争比较激烈的场景,其效果不佳。因为在竞争激烈的场合,最有可能的情况是每次都是不同的线程来请求相同的锁。这样偏向模式就会失效,因此在竞争激烈的应用,推荐关闭偏向锁。

-XX:+UseBiasedLocking 开启偏向锁(默认)

-XX:-UseBiasedLocking 关闭偏向锁

解锁:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

锁升级:线程2来竞争锁对象,线程1仍然存在此时偏向锁升级为轻量级锁。

轻量级锁

加锁:线程在执行同步块前,JVM会先当前栈帧中创建用于存储锁记录的空间,并将对象头中的Mark Work复制到锁记录中,官方称为Displaced Mark Word.然后线程尝试使用CAS将对象头中的Mark Word替换为执行锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其它线程竞争锁,当前线程便尝试使用自旋来获取锁,如果还不能获取锁则修改锁标识为重量级锁。

(Displaced Mark Word的作用:为了不想在lock与unlock这种底层操作上再加同步)

解锁:轻量级解锁时会使用CAS操作将Displaced Mark Word替换回到对象头,如果成功则表示没有竞争发生。如果失败,表示有线程在争夺锁并把锁标识修改为重量级锁,此时释放锁并唤醒等到的线程。

重量级锁

如果轻量级锁若干次自旋还不能获取锁,表示当前锁存在竞争,锁就会膨胀成重量级锁,因为自旋会消耗CPU,为了避免无用的自旋,一旦锁升级为重量级锁,就不会在恢复到轻量级锁状态,当锁处于这个状态时其它线程试图获取这个锁时都会被阻塞住,当持有锁的线程释放之后会唤醒这些线程,被唤醒的线程就会进入新一轮夺锁之争。

synchronized在汇编语言上如何实现?

做Java的人,有谁不知道马士兵?

还记得十年前初学Java的时候,就是他带我入坑的,哈哈。

感兴趣的朋友可以通过查看  博主主页  来免费获取

synchronized你以为你真的懂?synchronized在汇编语言上如何实现

我们再重温一下马士兵老师的口头禅:

“来,看我桌面。”

“豆芽子它长一房高,它也是一根菜。”

“骑着驴找马,但是不要虐待驴,别跟公司闹僵,公司也不会跟你过不去。”

“同学们用你们的大腿想想哪个最……?对,没错,就是这样,大家的大腿很发达。”

“不就是名字长一点吗,弗拉基米尔拉德马诺维奇其实简单来说就是列宁。”

马老师讲的视频还是很赞的,你们觉得呢?