问题背景

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

锁的状态

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁。锁的状态保存在对象的头文件中,以32位的JDK为例:

轻量级锁

“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

1、轻量级锁的加锁过程

(1)在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图2.1所示。

图2.1 轻量级锁CAS操作之前堆栈与对象的状态

(2)拷贝对象头中的Mark Word复制到锁记录中。

(3)拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(4),如果这个更新操作失败, 执行步骤(5)。

(4)如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图2.2所示。

图2.2 轻量级锁CAS操作之后堆栈与对象的状态

(5)如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。

2、轻量级锁的解锁过程:

(1)通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。

(2)如果替换成功,整个同步过程就完成了。

(3)如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

偏向锁

引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

1、偏向锁获取过程:

(1)访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态。

(2)如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。

(3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。

(4)如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。

(5)执行同步代码。

2、偏向锁的释放:

偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

3、重量级锁、轻量级锁和偏向锁之间转换

JVM 锁的其他优化

1、适应性自旋(Adaptive Spinning):

从轻量级锁获取的流程中我们知道,当线程在获取轻量级锁的过程中执行CAS操作失败时,是要通过自旋来获取重量级锁的。问题在于,自旋是需要消耗CPU的,如果一直获取不到锁的话,那该线程就一直处在自旋状态,白白浪费CPU资源。解决这个问题最简单的办法就是指定自旋的次数,例如让其循环10次,如果还没获取到锁就进入阻塞状态。但是JDK采用了更聪明的方式——适应性自旋,简单来说就是线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。

2、锁粗化(Lock Coarsening):

锁粗化的概念应该比较好理解,就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。举个例子:

package com.paddx.test.string;public class StringBufferTest {StringBuffer stringBuffer = new StringBuffer();public void append(){stringBuffer.append("a");stringBuffer.append("b");stringBuffer.append("c");}
}

这里每次调用stringBuffer.append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。

3、锁消除(Lock Elimination):

锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。看下面这段程序:

package com.paddx.test.concurrent;public class SynchronizedTest02 {public static void main(String[] args) {SynchronizedTest02 test02 = new SynchronizedTest02();//启动预热for (int i = 0; i < 10000; i++) {i++;}long start = System.currentTimeMillis();for (int i = 0; i < 100000000; i++) {test02.append("abc", "def");}System.out.println("Time=" + (System.currentTimeMillis() - start));}public void append(String str1, String str2) {StringBuffer sb = new StringBuffer();sb.append(str1).append(str2);}
}

虽然StringBuffer的append是一个同步方法,但是这段程序中的StringBuffer属于一个局部变量,并且不会从该方法中逃逸出去,所以其实这过程是线程安全的,可以将锁消除。

总结

本文重点介绍了JDK 中采用轻量级锁和偏向锁等对Synchronized的优化,但是这两种锁也不是完全没缺点的,比如竞争比较激烈的时候,不但无法提升效率,反而会降低效率,因为多了一个锁升级的过程,这个时候就需要通过-XX:-UseBiasedLocking来禁用偏向锁。

下面是这几种锁的对比:

参考资料

https://www.cnblogs.com/paddix/p/5405678.html

http://www.iteye.com/topic/1018932

http://www.infoq.com/cn/articles/java-se-16-synchronized

http://frank1234.iteye.com/blog/2163142

https://www.artima.com/insidejvm/ed2/threadsynch3.html

http://www.tuicool.com/articles/2aeAZn


Kotlin开发者社区

专注分享 Java、 Kotlin、Spring/Spring Boot、MySQL、redis、neo4j、NoSQL、Android、JavaScript、React、Node、函数式编程、编程思想、"高可用,高性能,高实时"大型分布式系统架构设计主题。

High availability, high performance, high real-time large-scale distributed system architecture design

分布式框架:Zookeeper、分布式中间件框架等
分布式存储:GridFS、FastDFS、TFS、MemCache、redis等
分布式数据库:Cobar、tddl、Amoeba、Mycat
云计算、大数据、AI算法
虚拟化、云原生技术
分布式计算框架:MapReduce、Hadoop、Storm、Flink等
分布式通信机制:Dubbo、RPC调用、共享远程数据、消息队列等
消息队列MQ:Kafka、MetaQ,RocketMQ
怎样打造高可用系统:基于硬件、软件中间件、系统架构等一些典型方案的实现:HAProxy、基于Corosync+Pacemaker的高可用集群套件中间件系统
Mycat架构分布式演进
大数据Join背后的难题:数据、网络、内存和计算能力的矛盾和调和
Java分布式系统中的高性能难题:AIO,NIO,Netty还是自己开发框架?
高性能事件派发机制:线程池模型、Disruptor模型等等。。。

合抱之木,生于毫末;九层之台,起于垒土;千里之行,始于足下。不积跬步,无以至千里;不积小流,无以成江河。


http://www.taodudu.cc/news/show-2355953.html

相关文章:

  • Kotlin 1.4 版本正式发布:新功能一覽
  • 怎样写好一篇高质量的技术文章?
  • 一切皆是文件:UNIX,Linux 操作系統的設計哲學
  • UML类图详细介绍
  • 川土微电子 | CA-IS3050U隔离式CAN收发器
  • 土木学matlab还是python_五行属土的字大全
  • 合抱之木,生于毫末。九层之台,起于累土。千里之行,始于足下
  • UML - 类图的关系总结
  • JAVA基础之异常
  • 九尺之台,始于垒土
  • Android 设计素材积累(九层之台起于垒土)
  • 初识Python必看基础知识~ 续(6)九层之台,起于垒土,肝肝肝~
  • 九层之台,起于垒土
  • 文学de垒土
  • 九层之台,始于垒土;合抱之木,始于毫末;千里之行,始于足下!
  • 合抱之木,生于毫末; 九层之台,起于垒土。
  • Android APP安全测试
  • 【电子商务法】北邮国际学院大三上期末复习
  • 大数据架构和模式
  • 2020上半年DeFi行业研究报告-Part2 发行 | TokenInsight
  • 独家研究 I 某新一线城市中高端养老社区项目(CCRC)入住客户画像深度洞察研究报告
  • Java基础:第5-6章(重点)
  • 刑事实务办案中疑难问题
  • 正确认识P2P,从容面对风暴
  • 当前网络安全风险及举例
  • 互联网金融学习总结(1)——互联网金融(ITFIN)概念相关学习
  • Chainlink的77种用法
  • 合同法律风险管理 被骗者刑事风险
  • 8 种流行的计算机视觉应用
  • 如何区分P2P是互联网创新还是非法集资?最高检回应

Java 并发编程:轻量级锁和偏向锁详解相关推荐

  1. Java并发编程:线程封闭和ThreadLocal详解

    什么是线程封闭 当访问共享变量时,往往需要加锁来保证数据同步.一种避免使用同步的方式就是不共享数据.如果仅在单线程中访问数据,就不需要同步了.这种技术称为线程封闭.在Java语言中,提供了一些类库和机 ...

  2. java并发编程之线程的生命周期详解

    java线程从创建到销毁,一共会有6个状态,不一定都经历,有可能只经历部分: NEW:初始状态,线程被创建,但是还没有调用start方法. RUNNABLED:运行状态,java线程把操作系统中的就绪 ...

  3. Java并发编程系列之CountDownLatch用法及详解

    背景 前几天一个同事问我,对这个CountDownLatch有没有了解想问一些问题,当时我一脸懵逼,不知道如何回答.今天赶紧抽空好好补补.不得不说Doug Lea大师真的很牛,设计出如此好的类. 1. ...

  4. Java网络编程(6)NIO - Channel详解

    前言 NIO的三个核心组件:Buffer.Channel.Selector Java网络编程(4)NIO的理解与NIO的三个组件完成了大概的了解 Java网络编程(5)NIO - Buffer详解详细 ...

  5. Java并发编程-ReentrantLock可重入锁

    目录 1.ReentrantLock可重入锁概述 2.可重入 3.可打断 4.锁超时 5.公平锁 6.条件变量 Condition 1.ReentrantLock可重入锁概述 相对于 synchron ...

  6. 多线程与并发编程入门基础,多图详解

    目录 1  概述 1.1 多线程概念 1.2 多线程的应用场景 2 线程与进程 2.1 概念 2.2 进程与线程的联系与区别: 3 并行并发 3.1 并行并发概念​ 3.2 并行并发 4 程序运行,线 ...

  7. 并发编程-09安全发布对象+单例模式详解

    文章目录 脑图 概述 安全发布对象的4种方式 示例 懒汉模式(线程不安全) 饿汉模式 静态域(线程安全) 改造线程不安全的懒汉模式方式一 静态方法使用synchronized修饰 (线程安全) 改造线 ...

  8. Java 异步编程 (5 种异步实现方式详解)

    同步操作如果遇到一个耗时的方法,需要阻塞等待,那么我们有没有办法解决呢?让它异步执行,下面我会详解异步及实现 @mikechen 目录 什么是异步? 一.线程异步 二.Future异步 三.Compl ...

  9. Java 并发编程之可重入锁 ReentrantLock

    Java 提供了另外一个可重入锁,ReentrantLock,位于 java.util.concurrent.locks 包下,可以替代 synchronized,并且提供了比 synchronize ...

  10. Java GUI编程:swing创建窗体代码详解

    package com.zxl;import java.awt.Container; import java.awt.FlowLayout;import javax.swing.JButton; im ...

最新文章

  1. 解决 后台播放音乐时,设置手机铃声,后台音乐不会暂停
  2. Linux的vim编辑器中的翻页命令
  3. ubuntu下gcc的安装与使用
  4. mac json格式化工具_简洁好用的工具都是相似的
  5. dell idrac 复位_DELL 服务器 装系统前初始化(恢复出厂、超线程、虚拟化、iDRAC设置)...
  6. Kubernetes之RBAC
  7. 关联规则挖掘算法_关联规则的挖掘与应用——Apriori和CBA算法
  8. Mysql 主从复制简易操作指南
  9. 微信步数修改.html,把微信步数修改到小
  10. 支付宝支付出现该商户未开通支付宝服务,无法付款
  11. pyhon身份证验证
  12. 最全面计算机英语单词列表(三)
  13. 静态路由 动态路由 php,静态路由汇总(路由聚合)
  14. 2022年腾讯课堂现在用m3u8下载不了,怎么办
  15. gxworks2使用指令手册_GX Works2操作手册(智能功能功能模块操作篇):三菱 GX 三菱 GX Works2操作手册(智能功能功能模块操作篇)...
  16. 让iPad认识一下Windows系统
  17. 电子工程师必备(电子书版3本全):
  18. 人生到处知何似,应似飞鸿踏雪泥
  19. ps如何批量处理图片
  20. 深富策略:市场热点不好把握 大概率窄幅震荡

热门文章

  1. adb操作命令详大全利用adb命令查看apk文件包名
  2. 经典面试问题回答思路
  3. 2016年高校保送生拟录取名单(清华大学)
  4. Silverlight载入动画(简易)
  5. 数据安全治理方法导论
  6. (20200226已解决)PyCharm里的黄色波浪线
  7. 中国最好的产品经理100人
  8. 阿里P7大牛手把手教你!java全栈工程师证书
  9. POJO、PO、DTO、DAO、BO、VO需要搞清楚的概念
  10. 至强服务器性能排行,至强cpu天梯图,至强服务器cpu排行-