博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深入浅出Java并发核心
阅读量:7009 次
发布时间:2019-06-28

本文共 2280 字,大约阅读时间需要 7 分钟。

一、引言

关于synchronized关键字,以前经常听说过两句话:synchronized是重量级锁;jdk1.6以后synchronized性能有较大的提升。那么,为什么称synchronized为重量级锁呢?jdk1.6又做了什么优化使得synchronized有很大的性能提升呢?

本文把之前的学习的synchronized锁优化整理下来。

二、为什么称为重量级锁?

我们都知道Object内部维护一个监视器Monitor,synchronized正是通过监视器锁实现同步的。监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

互斥锁:用于保护临界区,确保同一时间只有一个线程访问数据。对共享资源的访问,先对互斥量进行加锁,如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。

mutex的工作方式:

1) 申请mutex

2) 如果成功,则持有该mutex
3) 如果失败,则进行spin自旋.(不断发起mutex gets, 直到获得mutex或者达到spin_count限制为止)
4) 依据工作模式的不同选择yiled还是sleep
5) 若达到sleep限制或者被主动唤醒或者完成yield, 则重复1)~4)步,直到获得为止

由于Java的线程是映射到操作系统的原生线程之上的,如果要阻塞或唤醒一条线程,都需要操作系统来帮忙完成,这就需要从用户态转换到核心态中,因此状态转换需要耗费很多的处理器时间。

三、锁优化

自旋锁

通常情况下共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。可以让正在请求锁的那个线程执行一个忙循环,不会放弃处理器的时间片,看看持有锁的线程是否很快就会释放锁。这就是自旋锁的基本原理。

自旋等待虽然避免了线程切换的开销,但它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效果就会非常好,反之如果锁被占用的时间很长,那么自旋的线程反而会消耗处理器资源。如果自旋超过了限定的次数仍然没有成功获得锁,就应当挂起线程做切换。

自适应自旋

在JDK 1.6中引入了自适应的自旋锁。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。反之,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确。

锁消除

锁削除是指虚拟机即时编译器在运行时,对检测到的不可能存在共享数据竞争的锁进行削除。锁削除的主要判定依据来源于逃逸分析,如果判断到一段代码中,在堆上的所有数据都不会逃逸出去被其他线程访问到,那就不需要同步加锁。

有些时候,虽然没有显示使用锁,但是一些JDK的内置API或第三方jar包会存在隐藏的加锁动作,如StringBuffer的append()方法、Vector的add()方法。

锁粗化

如果一系列的连续操作都对同一个对象反复加锁和解锁,或者加锁操作是出现在循环体内,那即使没有出现线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。如果虚拟机检测到有一串零碎的操作都是对同一对象的加锁,将会把加锁同步的范围扩大到覆盖这些操作。

轻量级锁

轻量级锁的主要作用是在无实际竞争的情况下,减少传统的重量级锁的互斥消耗。当关闭偏向锁或者多个线程竞争偏向锁时,则会获取轻量级锁。

对象头部负责维护锁的状态,不同阶段的锁对应不同的状态,32位系统的格式如下:

轻量级锁的执行过程如下:

获取锁:
1. 判断当前对象是否处于无锁状态(01),若是,则JVM在当前线程的栈帧中建立一个Lock Record空间,用于存储锁对象当前的头部运行数据的拷贝;否则执行步骤(3);

图1
2. JVM利用CAS操作尝试将对象的头部运行数据更新为指向Lock Record的指针,如果操作成功则表示获取锁成功,则标记为轻量级锁状态(00),并执行执行同步操作;如果操作失败则执行步骤(3);
图2
3. 判断当前对象的头部运行数据是否指向当前线程的栈帧,如果是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;否则标识明该锁对象已经被其他线程抢占,这时轻量级锁需转变为重量级锁,锁标志位变成10;

释放锁:

1. 取出在获取轻量级锁保存在对象的头部运行数据;
2. 执行CAS替换当前对象的头部运行数据,如果成功,则说明释放锁成功,否则执行(3);
3. 如果CAS替换失败,说明有其他线程尝试获取该锁,则在释放锁时唤醒被挂起的线程。

偏向锁

轻量级锁是在无实际竞争的情况下使用CAS操作去消除互斥的性能消耗,偏向锁的目的,是在轻量级锁的前提下进一步提高程序的运行性能,减去CAS操作的消耗。 可以理解为锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)状态。

四种锁的状态转变: 无锁-->偏向锁-->轻量级锁-->重量级锁

转载地址:http://yfntl.baihongyu.com/

你可能感兴趣的文章
程序员们要向雷军学习
查看>>
Docker手动制作系统镜像
查看>>
我的友情链接
查看>>
js 网页实现计时器 监控鼠标定时报警
查看>>
11大Java开源中文分词器的使用方法和分词效果对比
查看>>
Windows 7部分程序图标不能正常显示的解决方法
查看>>
PMP中的技术
查看>>
文件处理工具常用方式
查看>>
Docker-数据卷和数据容器卷
查看>>
spring cloud zuul 入门
查看>>
java.lang.NoSuchMethodError: javax.xml.ws.WebFault.messageName()Ljava/lang/Strin
查看>>
Hystrix之ThreadLocal上下文传播
查看>>
卫星地图
查看>>
【撸码师备忘】事务的4种隔离级别与7种传播行为
查看>>
子类继承父类重写静态方法不能变成普通方法
查看>>
拒绝访问temp目录解决方法
查看>>
Confluence 6 审查日志的对象
查看>>
[程序员灯下黑]---没学会快乐
查看>>
android 地图 infoWindow的每个控件添加点击事件
查看>>
Nginx+uwsgi+Django (Python web环境)
查看>>