Java synchronized偏向锁的概念与使用

目录
  • 一、什么是偏向锁
  • 二、偏向锁原理
  • 三、偏向锁演示
  • 四、偏向锁的处理流程
  • 五、偏向锁的撤销
  • 六、偏向锁的好处

一、什么是偏向锁

HotSpot作者经过研究实践发现,在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低,引进了偏向锁。

偏向锁的“偏”,它的意思是锁会偏向于第一个获得它的线程,会在对象头(Mark Word中)存储锁偏向的线程ID,以后该线程进入和退出同步块时只需要检查是否为偏向锁、锁标志位以及ThreadID即可。

如下图是偏向锁对象头MarkWord布局:

二、偏向锁原理

偏向锁总是被第一个占用它的线程拥有,这个线程就是锁的偏向线程。在偏向锁的MarkWord中,就有一块区域用来记录偏向线程的ID,这个是在锁第一次被拥有的时候记录的。

因为记录了偏向线程ID,那么后续如果这个偏向线程进入和退出同步代码块的时候,就不需要再次加锁和解锁。

偏向锁再次进入同步代码块,是如何保证不需要再次加锁的?

偏向锁的线程会检查锁对象的MarkWord中是不是存放着自己的线程ID。

相等

表示偏向锁现在就是偏向当前线程的,无需再尝试获得锁。以后每次同步,都会检查锁的偏向线程ID与当前线程ID是否一致,一致就直接进入同步,省去了CAS去更新对象头的操作,提高了锁的性能。

不相等

表示偏向锁现在不是偏向当前线程的,此时发生了竞争,这时候当前线程就会尝试CAS去更新锁对象MarkWord中线程ID,尝试指定为自己的线程。此时也有两种情况:

1)、更新成功:表示已经将锁对象MarkWord中的线程ID替换为自己的线程ID,之前的线程可能刚好运行完,这时候锁重新偏向为当前线程,这种情况下,不会发生锁升级,锁仍然为偏向锁,只是偏向线程换成了另外一个线程;

2)、更新失败:表示之前的线程还在持有锁,那么当前线程只能一直CAS,当达到一定次数后,如果还没CAS成功,那么就会发生【偏向锁 -> 轻量级锁】的锁升级过程。

注意,偏向锁只有遇到其它线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁的。

三、偏向锁演示

public class BiasedLockDemo01 {
    public static void main(String[] args) {
        Object objLock = new Object();
        new Thread(() -> {
            synchronized (objLock) {
                System.out.println(ClassLayout.parseInstance(objLock).toPrintable());
            }
        }, "t1").start();
    }
}

如上我们看到,markword的倒数三位是000,根据前面的图,000表示的是轻量级锁,此时只有一个线程访问,为什么输出来的不是偏向锁标识101呢?

原因其实是偏向锁在Java 6之后是默认启用的,但在应用程序启动几秒钟(默认延迟4秒)之后才会激活,可以使用 -XX:BiasedLockingStartupDelay=0参数关闭延迟,让其在程序启动时立刻启动。当然为了演示,也可以在程序中休眠5秒,等待偏向锁激活后。

下面我们添加运行时JVM参数,再次启动程序,观察内存布局:

可以看到关闭偏向锁延迟后,当前锁就是偏向锁了。

四、偏向锁的处理流程

当线程第一次访问同步块并获取锁时,偏向锁处理流程如下:

1)、虚拟机将会把对象头中的是否偏向锁标志位设为“1”,即偏向模式;

2)、使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中 ,如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,效率极高;

五、偏向锁的撤销

前面提到,大部分情况下,锁都是被同一个线程获取到,持有偏向锁的线程不会主动释放锁。那么大部分情况下,不会涉及到偏向锁的撤销,当有另外的线程尝试竞争偏向锁的时候,这个时候才会涉及偏向锁的撤销流程。

举个例子,有线程A、线程B两个线程竞争获取锁,锁大部分情况下都被线程A持有,此时锁偏向于线程A,这样线程A每次进入/退出同步代码块,都无需再次加锁,只需判断MarkWord中保存的线程ID是否等于自己的线程ID。

线程A不会主动释放偏向锁,当运行了一段时间后,突然线程B过来尝试竞争这个偏向锁了,此时持有偏向锁的线程A会发生偏向锁的撤销。

偏向锁的撤销需要等待全局安全点(这个时间点没有代码正在执行),同时会检查持有偏向锁的线程A是否还在执行:

1)、线程A还是同步代码块中运行:发生【偏向锁 -> 轻量级锁】的升级过程。此时轻量级锁由原持有偏向锁的线程A持有,继续执行其同步代码,而正在竞争的线程B会在外面CAS自旋等待获取这个轻量级锁。

2)、线程A刚好执行完成同步代码块:则会将对象头MarkWord设置为无锁状态并撤销偏向锁,然后锁重新偏向到线程B,注意,这里会修改MarkWord中线程ID保存为线程B的线程ID。

流程图大体如下:

六、偏向锁的好处

偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一个锁的情况。偏向锁可以提高带有同步但无竞争的程序性能。它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问比如线程池,那偏向模式就是多余的,反而会影响效率。

注意,在JDK15中,已经废弃偏向锁。目前还是使用JDK1.8居多,所以我们还是有必要了解一下偏向锁。

到此这篇关于Java synchronized偏向锁的概念与使用的文章就介绍到这了,更多相关Java synchronized偏向锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java Synchronized的偏向锁详细分析

    目录 理论知识 源码论证 总结 上篇文章已经对Synchronized关键字做了初步的介绍,从字节码层面介绍了Synchronized关键字,最终字节码层面就是monitorenter和monitorexit字节码指令.并且拿Synchronized关键字和Java的JUC包下的ReentrantLock做了比较.Synchronized关键字的初体验-超链接地址 那么本篇文章将开始深入解析Synchronized关键字的底层原理,也就是解析Hotspot虚拟机对monitorenter和mon

  • Java面试synchronized偏向锁后hashcode存址

    目录 前言 1.hashcode是啥时候存进对象头中? 2.存在hashcode后,出现synchronized会是什么锁? 3.如果锁状态是 已偏向,再计算hashcode会怎样? 4.总结 前言 今天的文章从下面这张图片开始,这张图片Java开发们应该很熟悉了 我们都知道无锁状态是对象头是有位置存储hashcode的,而变为偏向锁状态是没有位置存储hashcode的,今天我们来通过实现验证这个问题:当锁状态为偏向锁的时候,hashcode存到哪里去了? 先说结论: jdk8偏向锁是默认开启,

  • Java synchronized偏向锁的核心原理详解

    目录 1.偏向锁的核心原理 2.偏向锁的撤销 3.偏向锁的膨胀 4.偏向锁的好处 总结 1. 偏向锁的核心原理 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作. Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现 这个线程 ID 是自己的就表示没有竞争,不用重新 CAS.以后只要不发生竞争,这个对象就归该线程所有. public class Main { static final Objec

  • Java synchronized重量级锁实现过程浅析

    目录 一.什么是重量级锁 二.重量级锁的演示 三.重量级锁的原理 四.锁的优缺点对比 一.什么是重量级锁 当有大量的线程都在竞争同一把锁的时候,这个时候加的锁,就是重量级锁. 这个重量级锁其实指的就是JVM内部的ObjectMonitor监视器对象: ObjectMonitor() { _header = NULL; //锁对象的原始对象头 _count = 0; //抢占当前锁的线程数量 _waiters = 0, //调用wait方法后等待的线程数量 _recursions = 0; //记

  • Java synchronized轻量级锁的核心原理详解

    目录 1.轻量级锁的原理 2.轻量级锁的分类 1.普通自旋锁 2.自适应自旋锁 3.轻量级锁的膨胀 总结 问题: 什么是自旋锁? 说一下 synchronized 底层实现原理? 多线程中 synchronized 锁升级的原理是什么? 1. 轻量级锁的原理 引入轻量级锁的主要目的是在多线程竞争不激烈的情况下,通过CAS机制竞争锁减少重量级锁产生的性能损耗.重量级锁使用了操作系统底层的互斥锁(Mutex Lock),会导致线程在用户态和核心态之间频繁切换,从而带来较大的性能损耗. 轻量级锁的使用

  • JAVA对象分析之偏向锁、轻量级锁、重量级锁升级过程

    在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分: 对象头(Header) 实例数据(Instance Data) 对齐填充(Padding). 对象头 HotSpot虚拟机(后面没有说明的话默认是这个虚拟机)对象头包括三部分: Mark Word 指向类的指针 数组长度(只有数组对象才有) 对象头之Mark Word Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关. Mar

  • 详解Java中的锁Lock和synchronized

    一.Lock接口 1.Lock接口和synchronized内置锁 a)synchronized:Java提供的内置锁机制,Java中的每个对象都可以用作一个实现同步的锁(内置锁或者监视器Monitor),线程在进入同步代码块之前需要或者这把锁,在退出同步代码块会释放锁.而synchronized这种内置锁实际上是互斥的,即没把锁最多只能由一个线程持有. b)Lock接口:Lock接口提供了与synchronized相似的同步功能,和synchronized(隐式的获取和释放锁,主要体现在线程进

  • 深入了解Java Synchronized锁升级过程

    目录 前言 对象结构 对象头 (1)无锁 (2)偏向锁 (3)轻量级锁 (4)重量级锁 对象体 对齐字节 锁升级 补充:Synchronized底层原理 EOF 前言 首先,synchronized 是什么?我们需要明确的给个定义——同步锁,没错,它就是把锁. 可以用来干嘛?锁,当然当然是用于线程间的同步,以及保护临界区内的资源.我们知道,锁是个非常笼统的概念,像生活中有指纹锁.密码锁等等多个种类,那 synchronized 代表的锁具体是把什么锁呢? 答案是—— Java 内置锁.在 Jav

  • 从汇编代码开始全面解析synchronized还原最真实的偏向锁

    目录 前言 一.TemplateTable::monitorenter() 二.lock_object(): 三.biased_locking_enter() 1).参数 2).概念 3).源码 前言 我们都知道java之所以跨平台能力强,是因为java在编译期没有被编译成机器码,而是被编译成字节码.早期的jvm会将编译好的字节码翻译成机器码解释执行,我们在jvm的源码中还可以看到早期的解释器——bytecodeInterpreter.cpp(虽然已经不再使用).对于字节码这种总数固定,解释逻辑

  • Java synchronized锁升级jol过程详解

    jol(java object layout)需要的依赖 <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.10</version> </dependency> 一.synchronized锁对象的升级(膨胀)过程主要如下: 1.膨胀过程:无锁(锁对象初始化时)-> 偏向

  • java synchronized 锁机制原理详解

    目录 前言: 1.synchronized 的作用: 2.synchronized 底层语义原理: 3. synchronized 的显式同步与隐式同步: 3.1.synchronized 代码块底层原理: 3.2.synchronized 方法底层原理: 4.JVM 对 synchronized 锁的优化: 4.1.锁升级:偏向锁->轻量级锁->自旋锁->重量级锁 4.1.1.synchronized 的 Mark word 标志位: 4.1.2.锁升级过程: 4.2.锁消除: 4.3

随机推荐