Synchronized关键字

概要

以下对Synchronized的描述来自互联网+书籍,解决了自己不理解的点,然后按自己的思路进行整理,便于后续学习和观看。

介绍和使用场景

Synchronized在java中是个关键字,用于构建同步代码,作用于方法,代码块

内部原理

从字节码角度来看Synchronized:

编写java代码

将idea生成的class文件直接放到Linux服务器,使用javap -c 进行反编译:

javap命令是jdk提供的反编译工具命令,常用参数区别如下:

总结:

synchronized修饰方法时,获取锁的方式是隐式的,进入同步方法的线程会先去判断是否有ACC_SYNCHRONIZED,有,就会去获取monitor对象(管程,又叫监视器锁),方法结束时,释放monitor对象。

synchronized修饰代码块时,monitorenter/monitorexit指令代表加锁和释放锁,也是对monitor对象的获取和释放。

synchronized是java内存模型(JMM)定义的操作中LOCK和UNLOCK的具体实现。

锁优化

jdk1.6之后,synchronized锁优化:偏向锁-->轻量级锁-->重量级锁(自旋锁发生在重量级锁状态下)

monitor监视器锁是重量级锁,JDK1.6之后,synchronized引入了偏向锁和轻量级锁。

原因:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得。

介绍锁前,先介绍下对象头--header中的mark word和监视器锁--monitor:

mark word

mark word 存储结构以及随着锁变化存储内容的变化:

monitor

重量级锁的时候,会为每一个对象生成一个monitor锁(synchronized锁实例对象时,为每个实例对象维护一个monitor,synchronized锁class对象时,所有实例对象公用一个monitor锁)。monitor有ObjectMonitor对象实现:

_header:对象头header中的mark word

_owner:记录获取锁的线程唯一标识

_WaitSet:双向链表,同步代码块中的被锁对象调用wait()方法时,当前线程释放锁,进入_WaitSet中

_cxq:单向链表,线程通过自旋获取锁失败,放入cxq链表中

_EntryList:双向链表,当前线程释放锁,从该链表中获取一个线程用于获取锁

重量级锁

重量级锁时,mark word锁标志位是10,同时,存储指向monitor锁的指针。出现场景是多线程竞争锁,执行同步代码。

之所以叫重量级锁,是因为java中,用户线程和内核线程是一对一映射的,用户线程的阻塞和线程的唤醒都需要操作系统内核线程来完成,会进行用户态和内核态之间的来回切换,同时需要保存线程的一些状态,这种操作特别耗时。

1.获取锁过程:

当线程获取monitor时,首先通过CASE去将_owner的值替换成当前线程,替换成功获取锁,替换失败,说明有线程已经占有了锁;占有锁,再判断_owner是否为当前线程,当前线程,重入值+1,非当前线程,进行自旋;自旋失败,放入_cxq单向链表,放入链表前再进行一次自旋。

2.释放锁过程:

当一个线程执行同步代码结束,要释放锁时,会去唤醒其他等待获取锁的线程;首先从_EntryList双向链表中获取线程,如果为null,则将_cxq中的线程插入_EntryList,然后再从_EntryList中获取线程。

3.调用wait()方法过程:

调用wait()方法,让当前线程释放锁,然后将线程存放到_WaitSet双向链表中。

4.调用notify/notifyAll过程:

调用notify/notifyAll方法,会将_WaitSet链表中的线程放入到_EntryList链表,等待释放锁的线程去唤醒来获取锁。

monitor锁的介绍获取/释放过程参考Java多线程:objectMonitor源码解析(4)_前后相随的博客-CSDN博客

轻量级锁

轻量级锁时,抢夺锁线程在栈中会生成 Lock Record 用于存储mark word,然后通过CAS操作,将mark word 替换成指向当前线程的指针,替换成功,即获取锁成功,mark word锁标志位是00,将mark word复制到Lock Record,Lock Record存储指向mark word的指针。出现场景是多线程,但不存在锁竞争的情况。

偏向锁

偏向锁时,锁标志位前一位+锁标志位是101,同时mark word存储当前线程号。出现场景是只有一个线程的情况。

偏向锁撤销过程:

对象一旦变成偏向锁,就不会主动释放,所以第二个线程看到对象处于偏向状态,说明这个对象已经存在竞争了,这个时候进行锁撤销,需要等到持有锁线程在安全点时,由JVM线程(JVM维护了一个集合保存存活的线程,通过遍历该集合判断是否存活)检查原来持有该对象锁的线程是否依然存活如果挂了,则可以将对象变为无锁状态,然后重新偏向新的线程;如果原来的线程依然存活,则马上执行那个线程的操作栈,检查该对象的使用情况,如果仍然需要持有偏向锁,则偏向锁升级(偏向锁就是这个时候升级为轻量级锁的);如果不在使用了,则可以将对象恢复成无锁状态,然后重新偏向。

“偏向锁就是这个时候升级为轻量级锁的” 针对这句话,有网友说,一个线程持有偏向锁,另一个线程获取失败,会升级轻量级锁;但是第三个线程过来,发现仍然获取失败,则升级为重量级锁。

当调用锁对象的hashcode方法,生成hashcode时,也会发生偏向锁撤销,因为偏向锁时,mark word中存储指向获取锁线程的指针和hashcode使用的是同一块mark word 空间。

安全点:

安全点就是代码的一些特殊位置,在这些位置,线程可以暂停,同时保存了一些其他位置没有的当前线程信息,提供给其他线程使用,如:线程上下文、对象的内部指针等。通常会根据“是否具有让程序长时间执行的特征”为标准。比如:选择些执行时间较长的指令作为Safe Point, 如方法调用、循环跳转和异常跳转等。(安全点不去了解那么深了)

安全点参考:一文掌握JVM Safe Point - 自由资讯

问题

轻量级锁和重量级锁都有重入锁,和偏向锁的重入有什么区别吗?

首先偏向锁,只需要第一次的时候进行CAS,将mark word本来存hashcode的地方替换成当前线程的指针,同一个线程再来获取锁时,只需要判断是否同一个线程,不需要再CAS,更快。

轻量级锁,线程的栈中都会创建一个Lock Record,通过CAS去替换mark word中的指针,替换失败,判断是否为同一个线程,同一个线程,此时的Lock Record 中复制的mark word 为null。即此时当前线程栈中会有两个Lock Record,一个有mark word 的值,一个为null;释放时,根据当前Lock Record值,判断是否完全是释放锁。轻量级锁重入会进行一次CAS,会慢一些。

重量级锁,通过CAS尝试替换monitor对象锁中_owner的值为当前线程,失败,会去比较_owner的值和当前线程是否为一个,同一个,重入值+1。重量级锁重入也会进行一次CAS,会慢一些。

参考文章:Java synchronized轻量级锁的核心原理详解-Finclip

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值