synchronized 锁的优化过程:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

一、不同锁对象的状态表示(需要了解 Java 对象头)

https://wiki.openjdk.java.net/display/HotSpot/Synchronization

二、关于 Lock Record(锁记录)

https://www.jianshu.com/p/fd780ef7a2e8

当字节码解释器执行 monitorenter 字节码轻量级锁锁住一个对象时,就会在获取锁的线程的栈上显式或者隐式分配一个 Lock Record 空间。

三、偏向锁

https://www.cnblogs.com/javaminer/p/3892288.html

在 JDK 1.6 中引入,默认启用,且会延迟启动,关闭延迟:-XX:BiasedLockingStartupDelay=0,关闭偏向锁:-XX:-UseBiasedLocking=false,默认会进入轻量级锁。

为了消除数据在无竞争(大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得)情况下的同步原语,提高程序的运行性能(消除这个线程锁重入(CAS)的开销)。

1.获取锁:

OpenJDK8 的 HotSpot 源码(markOop.hpp)中关于检测一个对象是否处于可偏向状态的源码

// 返回 true 时代表 markword 的可偏向标志 bit 位为 1 ,且对象头末尾标志为 01。
bool has_bias_pattern() const {
return (mask_bits(value(), biased_lock_mask_in_place) == biased_lock_pattern);
}
// 返回 NULL 时代表对象 Mark Word 中 bit field 域存储的 Thread Id 为空。
JavaThread* biased_locker() const {
assert(has_bias_pattern(), "should not call this otherwise");
return (JavaThread*) ((intptr_t) (mask_bits(value(), ~(biased_lock_mask_in_place | age_mask_in_place | epoch_mask_in_place))));
}
// 检测对象是否处于可偏向状态
bool is_biased_anonymously() const {
return (has_bias_pattern() && (biased_locker() == NULL));
}

若对象处于可偏向状态

  • JVM 使用 CAS 操作尝试将当前线程 ID 更新到对象头中。
  • 更新成功(判断对象是否获得偏向锁的条件:mark 字段后 3 位是101,thread 字段跟当前线程相同,epoch 字段跟所属类的 epoch 值相同),执行同步代码块内容。
  • 更新失败,表示该锁存在竞争且这个时候另外一个线程已获得偏向锁所有权。需要撤销偏向锁,改为轻量级锁。

若对象处于已偏向状态

  • 则检测 MarkWord 中存储的 Thread ID 是否等于当前 Thread ID 。
  • 相等, 证明该线程已经获取到偏向锁, 可直接继续执行同步代码块。
  • 不等, 证明该对象目前偏向于其他线程, 需要撤销偏向锁,改为轻量级锁。

关于撤销偏向锁(Revoke Bias)

当到达全局安全点(safepoint,此时间点, 没有线程在执行字节码,可以 stop the word)时获得偏向锁的线程被挂起,撤销偏向锁,改为轻量级锁,然后被阻塞在安全点的线程继续执行同步代码。

通过 MarkWord 中已经存在的 Thread Id 找到成功获取了偏向锁的那个线程, 然后在该线程的栈帧中建立 Lock Record 空间,然后就是轻量级锁获取锁的过程了。

2.释放锁:

一般锁在执行完同步代码块后, 都会有释放锁的操作, 而偏向锁并没有直观意义上的“释放锁”操作。

同步代码块执行完毕后只需要测试锁对象上的偏向锁模式是否还存在,如果存在则解锁成功,不需要额外的操作。

不会尝试将 Mark Word 中的 Thread ID 赋回原值 0 。这样做的好处是: 如果该线程需要再次对这个对象加锁,而这个对象之前一直没有被其他线程尝试获取过锁,依旧停留在可偏向的状态下, 即可在不修改对象头的情况下, 直接认为偏向成功。

关于重偏向(Rebias)

一个对象先偏向于某个线程, 执行完同步代码后, 另一个线程就不能直接重新获得偏向锁吗?

https://www.zhihu.com/question/56582060

答案是可以的,在方法区的每个类对象中存储着 epoch,当创建类实例对象时,实例对象中的 epoch 值来自类对象。

进入安全点时,若需要重偏向,会把类对象中 epoch 值增加,然后扫描所有持有该类实例对象的线程栈, 根据线程栈的信息判断出该线程是否锁定了该对象, 仅将改变后的 epoch 赋给被锁定的对象。

退出安全点后,当有线程需要尝试获取偏向锁时, 直接检查类实例对象中存储的 epoch 值与类对象中存储的 epoch 值是否相等, 如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。

疑问:什么时候需要重偏向,触发点是什么。

四、轻量级锁

轻量级锁一般(当禁用偏向锁时,执行同步代码会直接进入轻量级锁)是由偏向所升级而来。偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。

1.此时对象头状态

偏向锁撤销后,对象可能处于两种状态。

  • 获取偏向锁的线程若已经执行完同步代码块, 相当于原有的偏向锁已经过期无效了。此时对象被转换为不可偏向的无锁状态(未执行轻量级加锁流程)。
  • 获取偏向锁的线程若未经执行完同步代码块, 偏向锁依旧有效, 此时被转换为被轻量级加锁的状态(已执行轻量级加锁流程)。

2.获取锁:

  • 代码进入同步块的时,如果此同步对象没有被锁定(锁标志位为“01”),JVM 将在当前线程的栈帧中建立 Lock Record 空间,用于存储锁对象目前的 Mark Word 的拷贝(Displaced Mark Word)。
  • JVM 使用 CAS 操作尝试将锁对象的 Mark Word 中除锁标志位之外的空间更新为指向 Lock Record 的指针。
  • 更新成功(Mark Word 中的锁标志位会被设置为“00”),执行同步代码块内容。
  • 更新失败,JVM 会检查对象的 Mark Word 是否指向当前线程的栈帧。是就说明当前线程已获取锁,可直接进入同步块继续执行。否说明该锁对象已被其他线程获取。若此时只有两个线程竞争,则该线程会自旋获取锁。

3.释放锁:

  • 通过 CAS 操作来进行,如果对象的 Mark Word 仍然指向着线程的 Lock Record,那就用 CAS 操作把当前线程栈中的 Displaced Mark Word 拷贝回对象的 Mark Word 中。
  • 替换成功,即释放锁完成,整个同步过程也就完成了。
  • 替换失败,说明有其它线程(此时线程数 > 2)尝试过获取该锁(此时已经膨胀为重量级锁),那就直接释放锁,并唤醒被挂起(阻塞)的线程。

4.过程状态图:

https://www.oracle.com/technetwork/java/javase/tech/biasedlocking-oopsla2006-preso-150106.pdf

https://www.researchgate.net/publication/242536194_The_Hotspot_Java_Virtual_Machine

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

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

五、重量级锁(互斥锁)

若有两个以上的线程,那轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word 中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

https://www.cnblogs.com/jhxxb/p/10948653.html

六、锁的优缺点对比

优点 缺点 适用场景
偏向锁 加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比仅存在纳秒级的差距 若线程间存在锁竞争,会带来额外的锁撤销的消耗 只有一个线程访问同步块或者同步方法
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度 若线程长时间竞争不到锁,自旋会消耗 CPU 性能

线程交替执行同步块或者同步方法,追求响应时间,锁占用时间很短

重量级锁 线程竞争不使用自旋,不会消耗 CPU 线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗 追求吞吐量,锁占用时间较长

https://blog.csdn.net/lengxiao1993/article/details/81568130

https://icyfenix.iteye.com/blog/1018932

https://www.infoq.cn/article/java-se-16-synchronized

https://blog.dreamtobe.cn/2015/11/13/java_synchronized/

https://www.oracle.com/technetwork/java/biasedlocking-oopsla2006-wp-149958.pdf

Java-synchronized 中锁的状态及其转换的更多相关文章

  1. JAVA synchronized关键字锁机制(中)

    synchronized 锁机制简单的用法,高效的执行效率使成为解决线程安全的首选. 下面总结其特性以及使用技巧,加深对其理解. 特性: 1. Java语言的关键字,当它用来修饰一个方法或者一个代码块 ...

  2. 由Java 15废弃偏向锁,谈谈Java Synchronized 的锁机制

    Java 15 废弃偏向锁 JDK 15已经在2020年9月15日发布,详情见 JDK 15 官方计划.其中有一项更新是废弃偏向锁,官方的详细说明在:JEP 374: Disable and Depr ...

  3. java synchronized类锁,对象锁详解(转载)

    觉得还不错 留个记录,转载自http://zhh9106.iteye.com/blog/2151791 在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看 ...

  4. java synchronized究竟锁住的是什么

    刚学java的时候,仅仅知道synchronized一个线程锁.能够锁住代码,可是它真的能像我想的那样,能够锁住代码吗? 在讨论之前先看一下项目中常见关于synchronized的使用方法: publ ...

  5. Java synchronized到底锁住的是什么?

    使用环境:多线程java程序中. 作用:在多线程的环境下,控制synchronized代码段不被多个线程同时执行.synchronized既可以加在一段代码上,也可以加在方法上. 使用:synchro ...

  6. synchronized中锁是怎么升级的

    在JDK1.6以前,使用synchronized就只有一种方式即重量级锁,而在JDK1.6以后,引入了偏向锁,轻量级锁,重量级锁,来减少竞争带来的上下文切换. 锁升级主要依赖对象头中的Mark Wor ...

  7. 为什么JAVA线程中没有Running状态?

    面试官问:为什么 Java 线程没有 Running 状态?我懵了 —— 转  芋道源码 什么是 RUNNABLE? 与传统的ready状态的区别 与传统的running状态的区别 当I/O阻塞时 如 ...

  8. Java synchronized(this)锁住的是什么

    synchronized锁住的是括号里面的对象,而不是代码. 对于非static的synchronized方法,锁的就是对象本身,也就是this.

  9. JAVA体系的线程的实现,线程的调度,状态的转换

    java体系中线程的实现 1.使用内核线程实现 内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上,每个内核 ...

随机推荐

  1. Struts简单的实例

    一.创建Aciton类 package com.my.frame; public class HelloWordAction { private String name; public String ...

  2. docker容器里面安装php的redis扩展

      docker exec -i -t php /bin/bash 进入php容器内执行:pecl install -o -f redis  修改php.ini,添加:extension=redis. ...

  3. java_day04_数组

    chap04目标:数组---------------------------------------------- 1.概述 数组是一组数据的集合,数组中的每个数据被称为元素.在java中,数组也是对 ...

  4. 【转】(深入理解计算机系统) bss段,data段、text段、堆(heap)和栈(stack)

    bss段: bss段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域. bss是英文Block Started by Symbol的简称. bss段属于静态内存分配. ...

  5. zabbix 3.2.2 server端添加客户端主机配置 (四)

    一.添加主机 主机是Zabbix监控的基本载体,所有的监控项都是基于主机的,那么我们如何来添加一台被监控的主机呢? 1.首先要在被监控的主机上安装好zabbix_agent服务,并可以正常启动zabb ...

  6. 【获取url 问号后参数】防中文乱码

    function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&] ...

  7. shell命令学习记录

    id id会显示用户以及所属群组的实际与有效ID hostname 用来显示或者设置主机名(show or set the system’s host name).环境变量HOSTNAME也保存了当前 ...

  8. BPR贝叶斯个性化排序算法

    全序关系:集合中的任两个元素之间都可以比较的关系.

  9. PAT乙级1024

    题目链接 https://pintia.cn/problem-sets/994805260223102976/problems/994805297229447168 题解 第一遍也是没有全部AC,有3 ...

  10. Django学习系列19:完成最简单可用的网站——确保功能之间相互隔离

    前面遗留的问题,首先时功能测试运行结束后的清理:其次是目前我们的待办清单只允许创建一个大家公用的清单. 如何隔离测试,运行功能测试后待办事项一直存在于数据库中,这会影响下一次测试. 运行单元测试时,D ...