请说出你所知道的线程同步的方法。_Java程序员必知:你知道synchronized如何保证线程同步么?...

什么是synchronized?

synchronized是Java提供的一个并发控制的关键字。可以用来修饰方法和代码块。被synchronized修饰的代码块及方法,在同一时间,只能被单个线程访问。

synchronized有什么作用?

使用该关键字修饰的方法,在同一时刻最多只有一个线程可以进入。如果第一个线程获取锁进入了synchronized修饰的区域,在其释放锁之前,需要进入该实例中synchronized修饰的方法或者代码块的其他线程就需要等待,直到第一个线程释放锁之后,其他线程中才会有一个线程接着获取锁,进入互斥资源访问区。
通过词典查字面意思,是:adj. 同步的;同步化的。简而言之,参照Java内存模型,synchronized可以保证原子性、有序性和可见性。

synchronized原理

没有什么比源码更有说服力的了,我们在一个类中,分别使用synchronized来修饰方法和代码块,然后编译该类,再使用javap命令,分析汇编指令。

实战源码

6dc31beb806d959f3d6481b48eed537d.png

先执行:javac SynchronizedForJavap.java,对java文件进行编译,生成SynchronizedForJavap.class文件,然后执行:javap -v SynchronizedForJavap.class。部分输出结果如下:

6f508b53c2cb4986228a52cd3ea6429a.png

从上面的中文注释处可以看到,对于synchronized关键字而言,javac在编译时,会生成对应的monitorenter和monitorexit指令分别对应synchronized同步块的进入和退出,有两个monitorexit指令的原因是:为了保证抛异常的情况下也能释放锁,所以javac为同步代码块添加了一个隐式的try-finally,在finally中会调用monitorexit命令释放锁。而对于synchronized方法而言,javac为其生成了一个ACC_SYNCHRONIZED关键字,在JVM进行方法调用时,发现调用的方法被ACC_SYNCHRONIZED修饰,则会先尝试获得锁。
想要深入理解synchronized,还需要了解Java内存布局

Java内存布局

在这里,我们需要借助一个工具:

660d2984207c5cd527834790b6cfcbf9.png

JavaObjectLayOutDemo.java

fd4bc6e5f81f893b779fff8fb991bb69.png

输出结果如下:

0282883ba4f13f7a63c2daf2ec8b32d2.png

图:new Object内存布局

一个Java对象在内存中包括对象头、实例数据和补齐填充3个部分:

ce9a8bf62e0e5a2bc9a419e7791aedcf.png

图:Java对象内存布局

现在,加上一段代码,使用synchronized关键字,对obj对象加锁:

4152e8c62a1e44f999d03cb25614d519.png

输出结果如下:

52916e2bfc94a46ad29db079e6126c81.png

图:控制台输出-加上synchronized关键字后

bc22d7c8375a40a1be1ecd633d603c05.png

图:控制台输出结果前后对比-MarkWord部分发生了改变
由此得知:synchronized的锁,记录在对象的对象头中的Mark Word部分。

接下来,我们来看一下各种锁状态。

锁状态 new、偏向锁、轻量级锁、重量级锁

fbdc49d490e33ea061928e8411cf9ac1.png

图:HotSpot实现的锁状态对比表-synchronized
对应到控制台输出,对应位置如下:

253373fb5050a54359f3f3dd179f6f42.png

图:控制台输出-对象内存布局-锁的位置

锁状态说明及状态转换

Object obj = new Object();
锁 0 01 -> 无锁状态
默认synchronized(obj)
00 -> 轻量级锁
默认情况偏向锁有个时延,默认是4秒
why?因为JVM虚拟机自己有一些默认启动的线程,里面有好多sync代码,这些sync代码启动时就知道肯定会有竞争,如果使用偏向锁,就会造成偏向锁不断的进行锁撤销和锁升级的操作,效率较低。
如果设定-XX:BiasedLockingStartupDelay=0
new Object() -> 1 01
偏向锁 -> 线程ID为0 -> Anonymous BiasedLock
打开偏向锁,new出来的对象,默认就是一个可偏向匿名对象1 01
如果有线程上锁
上偏向锁,指的就是,把markword的线程ID改为自己线程ID的过程
偏向锁不可重偏向、批量偏向、批量撤销
如果有线程竞争
撤销偏向锁,升级轻量级锁
线程在自己的线程栈生成LockRecord,用CAS操作将markword设置为指向自己这个线程的LockRecord的指针,设置成功者得到锁
如果竞争加剧
竞争加剧: 有线程超过10次自旋,-XX:PreBlockSpin(在JDK7u40的时候这个指令消失了), 或者自旋线程数超过CPU核数的一半,1.6之后, 加入
自适应自旋Adapative Self Spinning,JVM自己控制
升级重量级锁: -> 向操作系统申请资源,linux mutex , CPU从3级-0级系统调用,线程挂起,进入等待队列,
等待操作系统的调度,然后再映射回用户空间
(以上实验环境是JDK11,打开就是偏向锁,而JDK8默认对象头是无锁)
下图是JDK1.6引入偏向锁之后的状态转换示意图:eb61df9aa1b4223643c1498873f72f69.png

  1. 图:偏向锁、轻量级锁的状态转化及对象MarkWord的关系-《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》

这样的状态转换,虽然脉络清晰了不少,但是是面向机器的。接下来,用一个实际生活场景,来阐述一下锁的各种状态转换。

bde0270ed098a5fc08a101a397b31d66.png

思维导图

7b86bf9bc3521e600e552dde5e2f574f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值