面试中多次被问到synchronized关键字的实现原理,一直认为仅是monitorenter与monitorexit两条指令而已,原来底层涉及到多种锁优化策略,包括:自旋锁,轻量锁,偏向锁。

1、自旋锁

互斥同步对性能影响最大的部分是线程的阻塞与恢复,因为这两个操作涉及用户态与内核态的转换。如果共享数据锁定时间很短,而且竞争不是特别激烈,那么阻塞实现并不划算。因此在多核的处理器中,我们可以让请求锁的线程进行忙等,而不是放弃处理器执行时间。反之,如果锁占用时间比较长,那忙等的线程只会白白浪费处理器资源,因此需要有一定的时间限制,超出限制便挂起线程。

使用AtomicReference模拟自旋锁:

public class SpinLock {

  private AtomicReference<Thread> sign =new AtomicReference<>();

  public void lock(){
Thread current = Thread.currentThread();
while(!sign .compareAndSet(null, current));
} public void unlock (){
Thread current = Thread.currentThread();
sign .compareAndSet(current, null);
}
}

2、轻量锁

轻量锁是相对于阻塞实现的重量锁而言的,很多时候虽然用到锁,但并不存在竞争情况,相当于多个线程轮流获取一把锁。轻量锁可使用CAS操作获取,被多线程竞争时会膨胀为重量锁。

每个对象的对象头中都有一个字,称为mark word,低两位代表状态,根据状态的不同,其余各位有不同的含义:

HotSpot虚拟机以一种叫做displaced headers的方法实现轻量锁。当线程获取轻量锁时,会在该线程的栈上创建一个lock record(lock record由一个mark word备份,一个指向对象的指针组成),然后备份当前mark word,最后使用CAS操作把mark word的状态位更新为Thin locked,也就是00,同时mark word中的指针指向lock record。

发生重入时,线程可以知道对象mark word中的指针正指向自己的栈,便创建一个值为NULL的lock record,在释放锁时如果lock record值为NULL就立即返回。

当轻量锁被一个线程获得后,另一个希望获取锁的线程会将轻量锁膨胀为重量锁,该线程在一个循环里不断尝试,直到膨胀成功。膨胀也会发生在锁的wait或notify方法被调用时,无论该锁是否已被一个线程获取。

进行膨胀时,首先用CAS将mark word中的状态位更新为Inflating,尝试获取处于该状态锁的线程会等待到膨胀完成,已经获取到锁的线程也必须等到膨胀完成才能释放锁。

3、偏向锁

前文说到轻量锁适合的场景是多个线程轮流占用资源,而偏向锁适用的场景是一个线程反复占用资源。什么时候会出现这种情况呢?比如在使用一些类库时,它的接口是线程安全的,但我们不会通过多个线程访问。

在Unlocked状态下,mark word的第三位代表是否允许启用偏序。如果那一位为0,说明锁没有被任何线程获取,且不允许偏序。如果为1,锁可以是如下状态中的一种:

  • 匿名偏向:线程指针为NULL,锁还没有偏向于任何一个线程,这是允许偏向的锁的初始状态。
  • 可重偏向:mark word中的epoch字段无效,获取锁的线程可使锁偏向自己。
  • 已偏向:线程指针不为NULL,epoch字段有效,锁正偏向于线程指针指向的线程。

由于偏向锁需要占用hashcode字段存放线程指针,访问该对象的hashcode将会导致偏向状态被撤销(Object类的hashcode的方法会导致这种情况)。

偏向锁使用的lock record与轻量锁一致,但是mark word备份没有被使用,在偏向被撤销时,会被转化为轻量锁。

偏序撤销在系统到达全局安全点时执行,竞争线程通过遍历偏序线程的lock record判断锁是否正在被占用, 将其转化为轻量锁或偏向自己。

参考:《Evaluating and improving biased locking in the HotSpot virtual machine》

HotSpot虚拟机的锁优化的更多相关文章

  1. 深入理解多线程(五)—— Java虚拟机的锁优化技术

    本文是<深入理解多线程>的第五篇文章,前面几篇文章中我们从synchronized的实现原理开始,一直介绍到了Monitor的实现原理. 前情提要 通过前面几篇文章,我们已经知道: 1.同 ...

  2. Java 虚拟机对锁优化所做的努力

    作为一款公用平台,JDK 本身也为并发程序的性能绞尽脑汁,在 JDK 内部也想尽一切办法提供并发时的系统吞吐量.这里,我将向大家简单介绍几种 JDK 内部的 "锁" 优化策略. 1 ...

  3. Java虚拟机对锁优化所做的努力(读书笔记)

    锁偏向      是一种加锁操作的优化手段,他的核心思想是:如果一个线程获得了锁,那么就进入偏向模式,当这个线程再次请求锁时,无须在做任何同步操作,因此在几乎没有锁竞争的场合,偏向锁是比较好的优化效果 ...

  4. Java虚拟机的锁优化

    1 锁偏向.当现成请求一个对象锁时,如果获得锁,则该对象锁进入偏向模式,当该线程再次请求该对象的锁时,无需再做任何同步操作. 可通过在Java虚拟机中开启参数-XX:+UseBasedLock开启偏向 ...

  5. 《深入理解Java虚拟机》-----第13章 线程安全与锁优化

    概述 在软件业发展的初期,程序编写都是以算法为核心的,程序员会把数据和过程分别作为独立的部分来考虑,数据代表问题空间中的客体,程序代码则用于处理这些数据,这种思维方式直接站在计算机的角度去抽象问题和解 ...

  6. 《深入了解java虚拟机》高效并发读书笔记——Java内存模型,线程,线程安全 与锁优化

    <深入了解java虚拟机>高效并发读书笔记--Java内存模型,线程,线程安全 与锁优化 本文主要参考<深入了解java虚拟机>高效并发章节 关于锁升级,偏向锁,轻量级锁参考& ...

  7. Java虚拟机13:互斥同步、锁优化及synchronized和volatile

    互斥同步 互斥同步(Mutual Exclusion & Synchronization)是常见的一种并发正确性保证手段.同步是指子啊多个线程并发访问共享数据时,保证共享数据在同一时刻只能被一 ...

  8. 深入理解java虚拟机(7)---线程安全 & 锁优化

    关于线程安全的话题,足可以使用一本书来讲解这些东西.<Java Concurrency in Practice> 就是讲解这些的,在这里 主要还是分析JVM中关于线程安全这块的内容. 1. ...

  9. 深入理解Java虚拟机读书笔记9----线程完全与锁优化

    九 线程完全与锁优化   1 Java语言中的线程完全         ---线程安全:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用 ...

随机推荐

  1. 连接池-Mybatis源码

    持续更新:https://github.com/dchack/Mybatis-source-code-learn Mybatis连接池 有这么个定律,有连接的地方就有池. 在市面上,可以适配Mybat ...

  2. 使用BaGet 搭建私有nuget 服务器

    使用BaGet 搭建私有nuget 服务器 netNugetBaGet 引言 为了增强代码的安全性和企业团队开发的高效性,搭建私有的package 包管理服务器是很有必要的,搭建私有的类库管理服务有以 ...

  3. makefile那些事儿

    一.好处 自动化编译,一条make命令,整个工程可以完全自动编译,make命令是构建大型项目的首选方案. makefile就像一个shell脚本一样,用来定义规则,一个名称包含一条或多条命令,在终端m ...

  4. 【NPDP笔记】第一章 新产品开发战略

    1.1 战略很重要 1.2 战略定义 使命/愿景/核心价值观:成为领导者 公司/经营战略:市场份额扩大10% 创新战略:强调技术,外部合作 职能战略:IT战略,人力资源战略 1.3明确组织方向 组织身 ...

  5. linux centos7 安装虚拟Python环境,pyenv安装文档

    python多版本控制pyenv安装文档 1.在线安装: curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-i ...

  6. 安卓学习日记第二天——Fragment

    一.基本概念 Fragment是依赖于Activity的,不能独立存在的. 一个Activity里可以有多个Fragment. 一个Fragment可以被多个Activity重用. Fragment有 ...

  7. 23 Collection集合常用方法讲解

    本文讲讲几个Collection的常用方法,这些方法在它的子类中也是很常用的,因此这里先拿出来单独讲解,以后它的子类中的这些方法就不再重复讲解. 几个常用方法: add() 添加一个元素 size() ...

  8. Linux下使用strip如何对库和可执行文件进行裁减

    如果生成的可执行文件或库比较大,这时候就可以使用strip命令进行裁减,在嵌入式开发中,如果使用的交叉编译工具是arm-linux,则命令 是arm-linux-strip,如果是arm-uclibc ...

  9. golang 之 go-micro

    在安装之前首先需要对go-micro有一定的了解 https://micro.mu/docs/cn/  go-micro中文文档 https://juejin.im/post/5cebafe6f265 ...

  10. JDK和J2EE有什么关系

    JDK(Java Development Kit)是Java 开发工具J2EE是Java一个平台 Java 平台有三个版本,这使软件开发人员.服务提供商和设备生产商可以针对特定的市场进行开发:* Ja ...