synchronized底层实现及优化机制(代码片段)

随心所向李先生 随心所向李先生     2022-10-23     121

关键词:

synchronized底层实现及优化机制

前言
首先介绍下synchronized锁,它属于互斥锁、悲观锁、同步锁、“重量级锁“”。它在1.6之前和1.6后加锁机制是不一样的。jdk1.6之前我简单说下流程,重点说下jdk1.6之后优化机制。

jdk1.6之前

可以看到synchronized加锁加的是内部的monitor对象,monitor是操作系统内部的对象,当多个线程一起访问synchronized修饰的代码块时,如果线程1先执行加锁的操作,然后执行业务方法,其他线程就会加不成功,然后阻塞在一个队列中,排队等待。

因为整个过程会发生线程阻塞、上下文切换、操作系统线程调度等,对性能有很大的影响。所以有了后面版本的改进

jdk1.6之后
引入了锁的几种状态分别是无状态、偏向锁、轻量级锁、重量级锁,其中偏向锁是1.6后才有的状态。线程锁会有个升级过程

下面来看下锁升级过程:

默认是无状态,
当一个线程访问synchronized修饰的代码块,首先看是否开启偏向锁,jdk默认是系统启动4秒后开启的。这个可以自己修改
如果开启了那么把当前线程id,放到对象头里,如果未开启则直接进入轻量级锁
当有第二个线程进来时候 线程从偏量锁到轻量级自旋锁
当自旋到一定次数 线程进入重量级锁(这个由jdk自己判断,由前一次同一个锁的自旋时间和锁的状态决定)


轻量级锁就是cas这种线程不会阻塞持续消耗着cpu, 重量级锁就是1.6之前那种线程会阻塞到一个队列中,那么偏向锁是什么呢?
简单来说就是把一个线程的id写到对象头里,下次这个线程再过来的时候就会判断这个id是否一样如果一样加锁成功,这就是偏向锁。
为什么会有这个概念或者状态呢,因为hotspot开发者经过调查发现我们的程序有一多半的时间都是一个线程在跑,这样我们就没有上来就开启重量级锁,因为开启重量级锁需要很多的操作,很消耗性能。
那么为什么轻量级锁在线程并发越来越多的时候要升级重量级锁呢,因为自旋锁的原理当加锁失败他不会切换线程它会不停地再次地尝试获取锁会持续消耗着cpu,会越来越耗费资源,到一定程度会超过线程切换的开销。

要点:整个过程是不可逆的 就是不能从重量级锁到轻量级 到偏量锁。

synchronized的整个升级过程都是对 对象头的操作,下面我们来看看对象的内部结构是怎么样的,
对象结构以及升级后的内部变化:
首先我们创建一个对象:

import lombok.Data;

@Data
public class Tcc 

    private Integer id;

    private String name;


再来看下这个结构图

1、实力数据就是我们在对象中创建的变量如id,name这些,这个很好理解。
2、对象头的话分为两部分MetaData元数据指针指向元数据占4个byte 这个这篇不做过多解释,和我们的锁无关,我们对象锁的信息存在对象头的Mark Word,它占8个byte,也就是64个bit,这64个bit会随着锁的升级而变化。数组长度是数组才有的部分,对象中没有。
3、对象填充就是当我们的对象整体size 不是8的倍数的话就会填充一些空间,以达到8的倍数,这样做是 为了查询效率牺牲空间换取性能。

为了方便大家的理解,我写个demo,来验证这些,首先我们需要导入一个依赖,这个依赖能够打印出我们的对象结构信息

 <dependency>
       <groupId>org.openjdk.jol</groupId>
        <artifactId>jol-core</artifactId>
        <version>0.10</version>
 </dependency>

代码:

 public static void main(String[] args)  

     
        Tcc tcc = new Tcc();
        System.out.println("无状态对象内部结构" + ClassLayout.parseInstance(tcc).toPrintable());

        Tcc tcc1 = new Tcc();
        Thread.sleep(5000);

        System.out.println("开启偏向锁后" + ClassLayout.parseInstance(tcc1).toPrintable());

        synchronized (tcc1) 
            System.out.println("升级偏向锁后" + ClassLayout.parseInstance(tcc1).toPrintable());

        
        Thread.sleep(1000);
        new Thread(() -> 
            synchronized (tcc1) 
                System.out.println("升级轻量级锁后" + ClassLayout.parseInstance(tcc1).toPrintable());
                try 
                    Thread.sleep(3000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        ).start();
        Thread.sleep(1000);
        new Thread(() -> 
            synchronized (tcc1) 
                System.out.println("升级重量级锁后" + ClassLayout.parseInstance(tcc1).toPrintable());

            
        ).start();
        

运行结果:

无状态对象内部结构com.teamer.servicetm.service.impl.Tcc object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int Tcc.id                                    0
     16     4   java.lang.String Tcc.name                                  null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

开启偏向锁后com.teamer.servicetm.service.impl.Tcc object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int Tcc.id                                    0
     16     4   java.lang.String Tcc.name                                  null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

升级偏向锁后com.teamer.servicetm.service.impl.Tcc object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           05 38 34 02 (00000101 00111000 00110100 00000010) (36976645)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int Tcc.id                                    0
     16     4   java.lang.String Tcc.name                                  null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

升级轻量级锁后com.teamer.servicetm.service.impl.Tcc object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           c8 f4 0f 1d (11001000 11110100 00001111 00011101) (487584968)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int Tcc.id                                    0
     16     4   java.lang.String Tcc.name                                  null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

升级重量级锁后com.teamer.servicetm.service.impl.Tcc object internals:
 OFFSET  SIZE               TYPE DESCRIPTION                               VALUE
      0     4                    (object header)                           da c9 48 19 (11011010 11001001 01001000 00011001) (424200666)
      4     4                    (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                    (object header)                           05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
     12     4                int Tcc.id                                    0
     16     4   java.lang.String Tcc.name                                  null
     20     4                    (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total


简单解释下 offset-起始位 size-所占的大小单位是byte type-类型(前两行是markword)value-前边是16进制后边是2进制bit,我们重点来看下这个value前两行这8个byte 64bit是怎么变化的,看的时候,需要倒着看也就是倒数第一个byte是第一个byte

无状态-最后三位 是001 第一个0代表 未开启偏向锁 01代表 是锁的标志位
偏向锁-最后三位 101 此时倒数第三位由0变成1锁标志位依然是01 不过前边的bit很多都变了,因为前边的bit存放了线程id
轻量级锁-最后三位 000 锁的标志位变成00可以看到虽然升级轻量级锁线程id信息不会清除
重量级锁-最后三位010 锁的标志位变成10

本文参考b站诸葛老师讲解,码字画图不易,欢迎大家一起讨论一起交流,点赞关注转发

synchronized底层实现原理及锁优化(代码片段)

synchronized底层实现原理及锁优化_Medlen-CSDN博客_synchronized底层原理一、概述1、synchronized作用原子性:synchronized保证语句块内操作是原子的可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同... 查看详情

synchronized底层实现原理及锁优化(代码片段)

synchronized底层实现原理及锁优化_Medlen-CSDN博客_synchronized底层原理一、概述1、synchronized作用原子性:synchronized保证语句块内操作是原子的可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同... 查看详情

synchronize底层实现原理以及相关的优化

首先来说下synchronize和Lock的区别:两者都是锁,用来控制并发冲突,区别在于Lock是个接口,提供的功能更加丰富,除了这个外,他们还有如下区别:synchronize自动释放锁,而Lock必须手动释放,并且代码中出现异常会导致unlock代... 查看详情

synchronize底层实现原理以及相关的优化(代码片段)

...:https://blog.csdn.net/zc19921215/article/details/84780335首先来说下synchronize和Lock的区别:两者都是锁,用来控制并发冲突,区别在于Lock是个接口,提供的功能更加丰富,除了这个外,他们还有如下区别:synchronize自动释放锁,而Lock必须... 查看详情

synchronized的实现原理及锁优化(代码片段)

... 记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized。对于当时的我们来说,synchronized是如此的神奇且强大。我们赋予它一个名字“同步”,也成为我们解决多线程情况的良药,百试不爽。但是,随着学习的深入,... 查看详情

java并发编程:底层实现机制

...e的应用1.volatile的定义与实现原理2.volatile的使用优化二、synchronized的应用1.锁的实现原理2.锁的对比2.1偏向锁2.2轻量级锁2.3锁的对比三、原子操作的实现原理1.术语2.处理器如何实现原子操作3.Java如何实现原子操作四、小结Java代码... 查看详情

java--synchronized与lock的区别及底层实现

起初Java 中只有synchronized这一种对程序加锁的方式,因此在JDK1.5之前,我们在编写并发程序的时候无一例外都是使用synchronized来实现线程同步的,而synchronized在JDK1.5之前同步的开销较大效率较低,因此在JDK1.5之后&... 查看详情

java多线程编程-(11)-从volatile和synchronized的底层实现原理看java虚拟机对锁优化所做的努力

...指令。下边我们对常见的实现同步的两个关键字volatile和synchronized进行底层原理的分析,分析之余我们就会了解到JVM在对锁的优化所做的事情,这样的话我们以后在使用这 查看详情

javasynchronized关键字的底层实现以及锁升级优化的原理一万字(代码片段)

介绍了synchronized关键字实现锁的底层原理以及JDK对于synchronized做出的锁升级优化!文章目录1syncronized基础知识1.1Synchronized锁的特性1.2synchronized锁表现形式1.3MarkWord1.4Monitor2synchronized块的底层原理3synchronized方法的底层原理4synchro... 查看详情

synchronize底层实现原理(代码片段)

????相信对Java程序员来说,synchronized关键字对大家来说并不陌生,当我们遇到并发情况时,优先会想到用synchronized关键字去解决,synchronized确实能够帮助我们去解决并发的问题,但是它会引起一些其他问题,比如最突出的一点就... 查看详情

java并发编程:synchronized底层优化(偏向锁轻量级锁)

...发编程系列:Java并发编程:核心理论 Java并发编程:Synchronized及其实现原理Java并发编程:Synchronized底层优化(轻量级锁、偏向锁)Java并发编程:线程间的协作(wait/notify/sleep/yield/join)Java并发编程:volatile的使用及其原理一、... 查看详情

synchronized理解及用法

...3.同步代码块,锁是括号里面的对象原理:JVM内置锁通过synchronized使用,通过内部对象Monitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的MutexLock(互斥锁)实现,它是一个... 查看详情

锁机制及锁优化

...制防止代码块受到并发访问的干扰:。java语言提供了一个synchronized(内部锁)或Lock/Condition(显示锁) 关键达到这一目的,在javaSE5.0引入了Lock/ReentranLock(重入锁)类。锁具有以下作用:   (1)锁用来保 查看详情

synchronized实现原理及其优化-(自旋锁,偏向锁,轻量锁,重量锁)(代码片段)

1.synchronized概述:  synchronized修饰的方法或代码块相当于并发中的临界区,即在同一时刻jvm只允许一个线程进入执行。synchronized是通过锁机制实现同一时刻只允许一个线程来访问共享资源的。另外synchronized锁机制还可以保证线... 查看详情

并发编程java并发机制的底层实现原理

volatile原理volatile是轻量级的synchronized,在多处理器开发中保证了共享变量的"可见性",volatile是一个轻量级的synchronized,在多CPU开发中保证了共享变量的“可见性”,也就是说当一个线程修改一个共享变量的时候,另一个线程能... 查看详情

synchronize偏向锁底层实现原理(代码片段)

1偏向锁的意义无多线程竞争时,减少不必要的轻量级锁执行路径。大多数情况下,锁不仅不存在多线程竞争,而且总是由同一条线程去多次获得锁,为了让线程获得锁的性能代价更低而引入了偏向锁。偏向锁主要... 查看详情

?synchronized底层实现---偏向锁

偏向锁入口synchronized分为synchronized代码块和synchronized方法,其底层获取锁的逻辑都是一样的。要找到锁的入口,就要找到代码中对monitorenter指令解析的地方。在HotSpot中有两处对monitorenter进行了解析:一个是在bytecodeInterpreter.cpp#18... 查看详情

java并发机制的底层实现和原理

...存的操作会将其他CPU里缓存了该内存地址的数据置为失效synchronized的实现原理  查看详情