我们都知道 synchronized 关键字能实现线程安全,但是你知道这背后的原理是什么吗?今天我们就来讲一讲 synchronized 实现线程同步背后的原因,以及相关的锁优化策略吧。

synchronized 背后的原理

synchronized 关键字经过编译之后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令,这两个字节码只需要一个指明一个要锁定或解锁的对象。如果 Java 程序中指明了对象参数,那么就用这个对象作为锁。

如果没有指定,那么就根据 synchronized 修饰的是实例方法还是类方法,去拿对应的对象实例或 Class 对象来作为锁对象。因此我们可以知道,synchronized 关键字实现线程同步的背后,其实是 Java 虚拟机规范对于 monitorenter 和 monitorexit 的定义。

在 Java 虚拟机规范对 monitorenter 和 monitorexit 的行为描述中,有两点需要特别注意。

  1. synchronized 同步块对同一条线程是可重入的,也就是不会出现自己把自己锁死的问题。
  2. 同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。

synchronized 关键字在 JDK1.6 版本之前,是通过操作系统的 Mutex Lock 来实现同步的。而操作系统的 Mutex Lock 是操作系统级别的方法,需要切换到内核态来执行。这就需要从用户态转换到内核态中,因此我们说 synchronized 同步是重量级的操作。

synchronized 锁优化

在 JDK1.6 版本中,HotSpot 虚拟机开发团队花了很大的精力去实现各种锁优化技术,如:适应性自旋、锁消除、锁粗话、偏向锁、轻量级锁等。其中最重要的是:自旋锁、轻量级锁、偏向锁这三个,我们重点讲这三个锁优化。

自旋锁与自适应自旋

对于重量级的同步操作来说,最大的消耗其实是内核态与用户态的切换。但很多时候,对于共享数据的操作时间可能很短,比内核态切换到用户态这个耗时还短。

于是有人就想:如果有多个线程并发去获取锁的时候,如果能让后面那个请求锁的线程「稍等一下」,不放弃 CPU 的执行时间,看看持有锁的线程是否会很快释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。 从理论上来看,如果所有线程都很快地获取锁、释放锁,那么自旋锁是可以带来较大的性能提升的。自旋锁在 JDK 1.4.2 中就已经引入,默认自旋 10 次。但自旋锁默认是关闭的,在 JDK 1.6 中才改为默认开启了。

自旋等待虽然避免了线程切换的开销,但还是要占用处理器的时间。如果锁被占用的时间段,自旋等待的效果就会非常好。但如果锁被长时间占用,那么自旋的线程就会白白消耗处理器的资源,从而带来性能上的浪费。

为了解决特殊情况下自旋锁的性能消耗问题,在 JDK1.6 的时候引入了自适应的自旋锁。 自适应意味着自旋时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者状态决定。如果在同一锁对象上,自旋等待刚刚成功获得过锁,那么虚拟机认为这次自旋也很有可能再次成功,进而允许线程自旋更长时间,例如自旋 100 个循环。

但如果对于某个锁,自旋很少成功获得过。那虚拟机为了避免浪费 CPU 资源,有可能省略掉自旋过程。有了自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对锁的状态预测就越准,虚拟机也会变得越来越聪明。

轻量级锁

轻量级锁是 JDK1.6 加入的新型锁机制,名字中的「轻量级」是相对于操作系统互斥量这个重量级锁而言的。轻量级锁诞生的原因,是由于对于绝大部分的锁而言,整个同步周期都不存在竞争。如果没有竞争的话,那就没必要使用重量级锁了,于是就诞生了轻量级锁来提高效率。

对于轻量级锁来说,其同步的流程如下:

  1. 在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为 01 状态),那么虚拟机会在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 拷贝。
  2. 虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。如果更新动作成功了,那么线程就泳衣了该对象的锁,并且对象 Mark Word 的锁标志位就变成了 00,表示此对象处于轻量级锁定状态。

简单地说,轻量级锁的同步流程可以总结为:使用 CAS 操作,在线程栈帧与锁对象建立双向的指针。

在没有线程竞争的情况下,轻量级锁使用 CAS 自旋操作避免了使用互斥量的开销,提高了效率。但如果存在锁竞争,除了互斥量的开销外,还额外发生了 CAS 操作。因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。

偏向锁

偏向锁是 JDK1.6 中引入的一项优化,它的意思是这个锁会偏向于第一个获得它的线程。如果在接下来的执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程将永远不需要再进行同步。 对于偏向锁来说,其同步流程如下所示:

  1. 假设当前虚拟机启动了偏向锁,那么当锁对象第一次被线程获取的时候,虚拟机将会把对象的锁标志位设置为 01,偏向锁位设置为 1。同时使用 CAS 操作将线程 ID 记录在对象的 MarkWord 之中。如果 CAS 操作成功,那么持有偏向锁的线程进入锁对应的同步块时,虚拟机将不再进行任何同步操作。
  2. 当有另外一个线程尝试去获取这个锁时,根据锁对象目前是否处于锁定状态,将其恢复到未锁定(01)或轻量级锁定(00)状态。随后的同步操作,就向上面介绍的轻量级锁那样执行。

可以看到偏向锁还是需要做一些 CAS 操作,但是对比起轻量级锁来说,其要设置的内容大大减少了,因此也提高了一些效率。偏向锁可以提高带有同步但无竞争的程序性能。 它同样是一个带有效益权衡(Trade Off)性质的优化,也就是说,它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问,那偏向模式就是多余的。

优化后的锁获取流程

经过 JDK1.6 的优化,synchronized 同步机制的流程变成了:

  1. 首先,synchronized 会尝试使用偏向锁的方式去竞争锁资源,如果能够竞争到偏向锁,表示加锁成功直接返回。
  2. 如果竞争锁失败,说明当前锁已经偏向了其他线程。需要将锁升级到轻量级锁,在轻量级锁状态下,竞争锁的线程根据自适应自旋次数去尝试抢占锁资源。
  3. 如果在轻量级锁状态下还是没有竞争到锁,就只能升级到重量级锁。在重量级锁状态下,没有竞争到锁的线程就会被阻塞。处于锁等待状态的线程需要等待获得锁的线程来触发唤醒。

上面的锁获取流程,可以用如下的示意图来表示:

总结

本文首先简单讲解了 synchronized 关键字实现同步的原理,其实是通过 Java 虚拟机规范对于 monitorenter 和 monitorexit 的支持,从而使得 synchronized 能够实现同步。而 synchronized 同步本质上是通过操作系统的 mutex 锁来实现的。由于操作操作系统 mutex 锁太过于消耗资源,因此在 JDK1.6 后 HotSpot 虚拟机做了一系列的锁优化,其中最重要的便是:自旋锁、轻量级锁、偏向锁。这三个锁的诞生原因,以及提升的点如下表所示。

现状 锁名称 收益 使用场景
大多数情况下,等待锁的时间比操作系统 mutex 短得多 自旋锁 减少内核态与用户态切换的开销 线程获取锁时间较短的情况
大多数情况下,锁同步期间没有线程竞争 轻量级锁 与自旋锁相比,减少了自旋时间 没有线程竞争锁
大多数情况下,锁同步期间没有线程竞争 偏向锁 与轻量级锁相比,减少了多余的对象复制操作 没有线程竞争锁

从上面表格可以看到,自旋锁、轻量级锁、偏向锁,他们的优化是逐渐深入的。

  1. 对于重量级锁来说,自旋锁减少了互斥量的内核、用户态切换开销。
  2. 轻量级锁,是自旋锁再 Java 内存模型里的直接应用,其同样是减少了内核态与用户态的切换开销。
  3. 偏向锁,相对于轻量级锁来说,减少了多余的对象复制操作,因此效率更高一些。

参考资料

老掉牙的 synchronized 锁优化,一次给你讲清楚!的更多相关文章

  1. synchronized锁优化

    1.自旋锁和自适应自旋锁 sync在JDK1.6之前之所以被称为重量级锁,是因为对于互斥同步的性能来说,影响最大的就是阻塞的实现.挂起线程与恢复线程的操作都需要转入内核态中完成.从用户态转入内核态是比 ...

  2. synchronized 锁优化

    synchronized 在jdk 1.7之前是重量级锁,独占锁,非公平锁.jdk1.7之后,synchronized引入了 偏向锁,自旋锁,轻量级锁,重量级锁 自旋锁 当线程在获取锁的时候,如果发现 ...

  3. synchronized锁详解

    synchronized的意义 解决了Java共享内存模型带来的线程安全问题: 如:两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?(针对这个问题进行分析 ...

  4. Java并发编程:synchronized和锁优化

    1. 使用方法 synchronized 是 java 中最常用的保证线程安全的方式,synchronized 的作用主要有三方面: 确保线程互斥的访问代码块,同一时刻只有一个方法可以进入到临界区 保 ...

  5. Synchronized锁性能优化偏向锁轻量级锁升级 多线程中篇(五)

    不止一次的提到过,synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchro ...

  6. Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  7. 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现

    一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...

  8. 并发-Synchronized底层优化(偏向锁、轻量级锁)

    Synchronized底层优化(偏向锁.轻量级锁) 参考: http://www.cnblogs.com/paddix/p/5405678.html 一.重量级锁 上篇文章中向大家介绍了Synchr ...

  9. 【转】Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)

     一.重量级锁 上篇文章中向大家介绍了Synchronized的用法及其实现的原理.现在我们应该知道,Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的.但是监视器锁本 ...

随机推荐

  1. MySQL基础合集

     我的小站 1.MySQL的优势 运行速度快 使用成本低 可移植性强 适用用户广 2.MySQL的运行机制 一个SQL语句,如select * from tablename ,从支持接口进来后,进入连 ...

  2. 硬件开发笔记(一):高速电路设计Cadence Aleego软件介绍和安装过程

    前言   红胖子软硬通吃的前提的使用AD,涉及到高速电路板,要配合高速硬件工程师,使用Aleegro更合适,遂开启了Aleegro设计电路板学习,过程保存为开发笔记,旨在普及和沟通技术,共同进步,学无 ...

  3. 从零开始学YC-Framework之初步

    本文主要内容为如下几个方面? YC-Framework的取名出于什么考虑? YC-Framework的特点有哪些? YC-Framework的模块由哪些组成? 为什么要开发YC-Framework? ...

  4. MVC 的dao层、service层和controller层

    1.dao层 dao层主要做数据持久层的工作, 负责与数据库进行联络的一些任务都封装在此 ,dao层的设计 首先 是设计dao层的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可以再模 ...

  5. filter/backdrop-filter 毛玻璃效果

    对于方式二采用的方式,如果存在边缘模糊程度不够,可以设置扩大伪元素范围(margin: -20px),父元素超出裁剪(overflow: hidden). <!DOCTYPE html> ...

  6. NLP教程(5) - 语言模型、RNN、GRU与LSTM

    作者:韩信子@ShowMeAI 教程地址:http://www.showmeai.tech/tutorials/36 本文地址:http://www.showmeai.tech/article-det ...

  7. Dnscat2隧道

    0x01 前言   DNS是用来做域名解析的,是连接互联网的关键,故即使是企业内网,在防火墙高度关闭下,也有着很好的连通性,但是黑客却可以通过将其他协议的内容封装再DNS协议中,然后通过DNS请求和响 ...

  8. RocketMq 完整部署

    目录 RocketMq 部署 环境 物理机部署 自定义日志目录 自定义参数和数据存放位置 服务启动 启动name server 启动broker 关停服务 尝试发送消息 常见报错 部署 rockerm ...

  9. Python写安全小工具-TCP全连接端口扫描器

    通过端口扫描我们可以知道目标主机都开放了哪些服务,下面通过TCP connect来实现一个TCP全连接端口扫描器. 一个简单的端口扫描器 #!/usr/bin/python3 # -*- coding ...

  10. 791. Custom Sort String - LeetCode

    Question 791. Custom Sort String Solution 题目大意:给你字符的顺序,让你排序另一个字符串. 思路: 输入参数如下: S = "cba" T ...