Java中synchronized锁升级的过程

目录
  • 简介
  • CAS
  • markWord
  • Synchronized的锁升级
  • 偏向锁
  • 轻量级锁
  • 重量级锁
  • 总结

简介

在多线程中解决线程安全的问题时常用到Synchronized,现在的synchronized相对于早期的synchronized做出了优化,从以前的加锁就是重量级锁优化成了有一个锁升级的过程(偏向锁->轻量级锁->重量级锁)。

CAS

cas的全称是compare and swap,从名称上可以看出它是先比较再进行设置,它是一种在多线程环境下实现同步功能的机制。

下面这段代码是在ReentrantLock类中复制的一段关于CAS操作的代码

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

compareAndSwapInt的参数,这里的参数一和参数二现在把他理解成是一个参数unsafe.compareAndSwapInt(curr, expect, update);所以这一个Cas操作里面需要三个参数

  • 参数一:当前值
  • 参数二:期望值
  • 参数三:需要修改成的值

只有在当前值和期望值一致的时候才会将当前值修改成参数三所传入的值。

CAS在JUC包中应用很广泛,比如在AtomicXXX类中使用到了大量的CAS操作,

CAS不是很难理解,有个概念就好。

markWord

如果了解对象的内存布局的可以略过此段。这个对象的内存布局是和JVM的实现有关,本章所说的是HotSpot的实现。

当一个对象被创建出来后它在内存中的布局如下,由四部分组成:

  • 8个字节的markword,(markword里面包含了其它的东西,比如GC标记,锁类型)
  • 4个字节的ClassPoint(此指针指向的Class),默认是开启指针压缩所以是四个字节,关闭指针压缩后是八个字节
  • 实例对象中的成员属性大小
  • 字节填充(有的JVM需要8字节对齐,如果上面的字节相加后不能被8整除,则需要在此补齐)

看到上面的图,应该可以大概的看出来synchronized加锁,其实就是修改的对像头里面的markword的数据。所以synchronized可以对任何一个对象加锁

现在有一个Java类T,将它new出来之后它的对象的内存布局是什么样子的呢?

class T{
    Integer age;
}

可以通过一个小工具来查看下这个T类在内存中的对象布局

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
    <scope>compile</scope>
</dependency>

通过下面的程序来打印下T对象的布局是什么样子的。

public static void main(String[] args) {
    T o = new T();
    System.out.println(ClassLayout.parseInstance(o).toPrintable());
}

这张图是一个没有加锁的对象的对象布局。

通过synchronized后的对象布局是什么样子的呢?这次再修改下T类,目的是让它存在字节填充

class T{
    Integer age;
    Integer age1;
}
public static void main(String[] args) {
    T o = new T();
    synchronized (o){
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }
}

到这里可能有些小伙伴有疑问,这里为啥是轻量级锁,不应该先是偏向锁吗?原因如下:

因为偏向锁是有4秒的延迟的,所以如果想要看到效果可以在代码里加上sleep(4100)就可以了。或者是通过jvm参数-XX:BiasedLockingStartupDelay=0将延迟设置成0

看完这里也对markword有了些了解了,因为在synchronized中加锁就是通过cas的方式修改的markword中的锁状态

Synchronized的锁升级

上图大概就是Synchronized加锁后的一个锁升级的过程。从早期的重量级锁优化到了现在一个轻量级锁。

偏向锁

上面的重量级锁说到重量级锁想要申请一把锁需要用户态到内核态的一个转换,到了后期的JDK版本中,加锁不用在去向OS去申请锁了,只需要在用户态就可以完成加锁。

从名字上可以看出偏向锁它就是偏向某一个线程,把这个锁加到这个线程上,在加锁的时候如果发现当前锁的竞争线程只有一个线程的话,那么这个锁直接偏向这个线程。直接上锁,不存在竞争。并在线程栈中创建一个LR(锁记录)并将markword拷贝到LR中,同时锁中的markwrod中的指针也会指向当前持有锁线程的LR

这里的LR是有什么作用?

首先synchronized是一个可重入锁,它即然是一个可重入锁它就得有一个东西用来记录重入的次数(加锁几次必须解锁几次)。在解锁时LR在栈中弹出一个就表示解锁一次。

当有多个线程竞争的时候会升级成轻量级锁(自旋锁)

通过下图来看下偏向锁是怎么一回事。

当大呆需要上WC时,只有它自已要上WC,此时并没有其它的人需要上WC,那么这时这个WC可以直接给大呆使用,并且大呆把可以标识自已身份的ID贴到门上,表示此时大呆占用了这个WC。

当又有一个线程来抢占锁时发现当前锁已被占用,此时锁会从偏向锁升级成轻量级锁。

匿名偏向

在执行的时候将偏向锁的延迟设置成0-XX:BiasedLockingStartupDelay=0

 public static void main(String[] args) throws InterruptedException {
     T o = new T();
     System.out.println(ClassLayout.parseInstance(o).toPrintable());
 }

可以看这个程序的执行结果,当前的锁状态是偏向锁,而有意思是的锁存在,但是他并没有指向线程的指针,

这种情况称为匿名偏向。

轻量级锁

说到轻量级锁可能需要在两种情况下来说它,一是在升级成轻量锁之前有偏向锁,另一种是在升级轻量锁之前没有偏向锁,这里说完第一种第二种不用解释各位也会明白是怎么一回事。

还是用上面这个图来解释,此时当前的WC被大呆所占用,这时二呆来了也要使用WC。这时大呆和二呆就要通过CAS的方式来抢占WC。

因为此时锁的状态是偏向锁的状态,二呆来了也要使用WC(这时有两个人同时要使用WC,这时就要将偏向锁升级成轻量级锁),在升级轻量锁之前首先需要将WC上的标识大呆身份的ID撕下来(这一步叫做偏向锁的撤销),然后能过自旋+CAS的方式两个人来抢锁。当其中一个线程抢锁成功后,会将LR贴到WC的门上,表示WC当前被某个线程占用,然后另一个没有抢到锁的线程就一直自旋,当自旋一定次数后升级成重量级锁。

如果在升级轻量锁之前没有偏向锁,此时两个线程直接自旋+CAS的方式来抢锁。

重量级锁

在了解重量级锁之前,我想应该先说下用户态与内核态

对于系统而言,它可以做的一些事情,普通的应用程序是无法完成的,比如系统可以干掉硬盘,如果普通的程序想要干掉硬盘它必须向操作系统去申请,由此操作系统中的指令分了级别,操作系统级别可以访问所有的指令,在用户态下只能访问用户能访问的指令,如果用户态要访问内核态可以执行的指令必须去向操作系统去申请,请操作系统调用。

在JDK早期,上锁只能上重量级锁。因为,所谓的JVM其实它也是工作在用户态的一个进程,如果想要对一个对象进行上锁,那它必须去向系统去申请锁。申请锁成功后,还需要将这把锁从内核态返回到用户态,它称为重量级锁的原因就是在锁申请的时候都要有一个在用户态到内核态的转换

当抢占到锁后,markword里面记录的不再是LR的指针,而是指向的是一个C++的对象ObjectMonitor,

如果当前线程自旋一段时间后没有抢到锁就会升级成重量级锁,并将当前的线程存入EntryList队列中阻塞,持有锁的线程执行完成后,在唤醒EntryList队列中的线程去抢占锁。

总结

synchronized的锁升级过程:

  • 偏向锁未启动,创建出来的是普通对象, 如果有一个线程来抢占锁,该锁偏向此线程,这时升级为偏向锁。

    • 在偏向锁的基础上又来一个线程抢占锁此时升级为轻量级锁。当一个线程没有抢占到锁,并且自旋了一定时间后还没有抢到锁,就会升级成重量级锁。
  • 在偏向锁的基础上如果出现了重度竞争就会直接升级成重量级锁
  • 偏向锁已启动,创建出来的对象匿名偏向,后面的锁升级和上面写的一样。

到此这篇关于Java中synchronized锁升级的过程的文章就介绍到这了,更多相关synchronized锁升级内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

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

  • java中synchronized锁的升级过程

    目录 synchronized锁的升级(偏向锁.轻量级锁及重量级锁) java同步锁前置知识点 synchronized同步锁 java对象头 偏向锁 轻量级锁 重量级锁 关于自旋锁 打印偏向锁的参数 synchronized原理解析 一:synchronized原理解析 1:对象头 2:Synchronized在JVM中的实现原理 三.锁的优化 1.锁升级 2.锁粗化 3.锁消除 synchronized锁的升级(偏向锁.轻量级锁及重量级锁) java同步锁前置知识点 1.编码中如果使用锁可以

  • Java中synchronized锁升级的过程

    目录 简介 CAS markWord Synchronized的锁升级 偏向锁 轻量级锁 重量级锁 总结 简介 在多线程中解决线程安全的问题时常用到Synchronized,现在的synchronized相对于早期的synchronized做出了优化,从以前的加锁就是重量级锁优化成了有一个锁升级的过程(偏向锁->轻量级锁->重量级锁). CAS cas的全称是compare and swap,从名称上可以看出它是先比较再进行设置,它是一种在多线程环境下实现同步功能的机制. 下面这段代码是在Re

  • Java Synchronized锁升级原理及过程剖析

    目录 前言 工具准备 对象的内存布局 锁升级过程 偏向锁 轻量级锁 重量级锁 总结 前言 在上篇文章深入学习Synchronized各种使用方法当中我们仔细介绍了在各种情况下该如何使用synchronized关键字.因为在我们写的程序当中可能会经常使用到synchronized关键字,因此JVM对synchronized做出了很多优化,而在本篇文章当中我们将仔细介绍JVM对synchronized的各种优化的细节. 工具准备 在正式谈synchronized的原理之前我们先谈一下自旋锁,因为在s

  • Java中synchronized关键字引出的多种锁 问题

    前言 Java 中的 synchronized关键字可以在多线程环境下用来作为线程安全的同步锁.本文不讨论 synchronized 的具体使用,而是研究下synchronized底层的锁机制,以及这些锁分别的优缺点. 一 synchronized机制 synchronized关键字是JAVA中常用的同步功能,提供了简单易用的锁功能. synchronized有三种用法,分别为: 用在普通方法上,能够锁住当前对象.用在静态方法上,能够锁住类用在代码块上,锁住的是synchronized()里的对

  • 详解Java中的锁Lock和synchronized

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

  • 透彻理解Java中Synchronized(对象锁)和Static Synchronized(类锁)的区别

    本文讲述了Java中Synchronized(对象锁)和Static Synchronized(类锁)的区别.分享给大家供大家参考,具体如下: Synchronized和Static Synchronized区别 通过分析这两个用法的分析,我们可以理解java中锁的概念.一个是实例锁(锁在某一个实例对象上,如果该类是单例,那么该锁也具有全局锁的概念),一个是全局锁(该锁针对的是类,无论实例多少个对象,那么线程都共享该锁).实例锁对应的就是synchronized关键字,而类锁(全局锁)对应的就是

  • java中synchronized Lock(本地同步)锁的8种情况

    目录 lock1 lock2 lock3 lock4 lock5 lock6 lock7 lock8 Lock(本地同步)锁的8种情况总结与说明: * 题目: * 1.标准访问,请问是先打印邮件还是短信 Email * 2.email方法新增暂停4秒钟,请问是先打印邮件还是短信 Email * 3.新增普通的hello方法,请问先打印邮件还是hello hello * 4.两部手机,请问先打印邮件还是短信 SMS * 5.两个静态同步方法,1部手机,请问先打印邮件还是短信 Email * 6.两

  • Java中synchronized实现原理详解

    记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字"同步",也成为了我们解决多线程情况的百试不爽的良药.但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它. 诚然,随着Javs SE 1.6对synchronized进行的各种优化后,synchronized并不会显得那么

随机推荐