【JVM】synchronized与锁升级

文章目录

    • 1. synchronized锁优化背景
    • 2. synchronized锁性能优化过程
      • 2.1 java5以前
      • 2.2 monitor锁
      • 2.3 java6开始
    • 3. 无锁
    • 4. 偏向锁
      • 4.1 背景
      • 4.2 理论落地
      • 4.3 技术实现
      • 4.4 偏向锁的撤销
      • 4.5 题外话
    • 5. 轻量级锁
      • 5.1 轻量级锁的加锁
      • 5.2 轻量级锁的释放
      • 5.3 锁升级
    • 6. 重量级锁
    • 7. 锁升级与hashCode
    • 8. 总结
    • 9. JIT编译器对锁的优化
      • 9.1 锁消除
      • 9.2 锁粗化

1. synchronized锁优化背景

【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁,能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。

说明: 尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法

用锁能够实现数据的安全性,但是会带来性能下降。无锁能够基于线程并行提升程序性能,但是会带来安全性下降。如何求得平衡?

  • 锁的升级过程
    1. 无锁
    2. 偏向锁
    3. 轻量级锁
    4. 重量锁

synchronized锁:由对象头中的Mark Word根据锁标识位的不同而被复用及锁升级策略

在这里插入图片描述

2. synchronized锁性能优化过程

2.1 java5以前

  • 只有Synchronized,这个是操作系统级别的重量级操作;

  • 重量级锁在锁的竞争比较激烈的情况下,性能会下降;

  • 用户态和内核态之间的切换

java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

在Java早期版本中,synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的MutexLock(系统互斥量)来实现的,挂起线程和恢复线程都需要转入内核态去完成,阳寒或唤醒一个Java线程需要操作系经切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因

2.2 monitor锁

  • Monitor可以理解为一种同步工具,也可理解为一种同步机制,常常被描述为一个Java对象。Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁

  • Monitor是在jvm底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,状态转换需要的处理器时间成本非常高。所以synchronized是Java语言中的一个重量级操作。

  • Monitor与java对象以及线程是如何关联?

    1. 如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址
    2. Monitor的Owner字段会存放拥有相关联对象锁的线程id

2.3 java6开始

为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁

  • 偏向锁:Mark Word存储的是偏向的线程ID
  • 轻量级锁:Mark Word存储的是指向线程栈中Lock Record的指针
  • 重量级锁:Mark Word存储的是指向堆中的monitor对象的指针

在这里插入图片描述

3. 无锁

public class Main {public static void main(String[] args) {Object obj = new Object();System.out.println(ClassLayout.parseInstance(obj).toPrintable());System.out.println("16进制:" + Integer.toHexString(obj.hashCode()) + "\n");System.out.println("2进制:" + Integer.toBinaryString(obj.hashCode()) + "\n");System.out.println(ClassLayout.parseInstance(obj).toPrintable());}
}

看value时需要从右往左从下往上看字节顺序,比特顺序则从左往右看

java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total16进制:f2a0b8e2进制:1111001010100000101110001110java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 8e 0b 2a (00000001 10001110 00001011 00101010) (705400321)4     4        (object header)                           0f 00 00 00 (00001111 00000000 00000000 00000000) (15)8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

4. 偏向锁

偏向锁:单线程竞争

当线程A第一次竞争到锁时,通过操作修改Mark Word中的偏向线程ID、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步

主要作用:当一段同步代码一直被同一个线程多次访问由于只有一个线程那么该线程在后续访问时便会自动获得锁,而无需进行加锁及释放锁

4.1 背景

Hotspot 的作者经过研究发现,大多数情况下:
多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一个线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决在只有在一个线程执行同步时提高性能

小总结:

偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。也即偏向锁在资源没有竞争情况下消除了同步语句,懒的连CAS操作都不做了,直接提高程序性能

4.2 理论落地

在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程。

那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接会去检查锁的Mark Word里面是不是放的自己的线程ID)。

  • 如果相等,表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁凡乎没有额外开销,性能极高。
  • 如果不等,表示发生了竞争,锁已经不是总是偏向于同一个线程了,这个时候会尝试使用CAS来替换Mark Word里面的线程ID为新线程的ID
  • 竞争成功,表示之前的线程不存在了, Mark Word里面的线程ID为新线程的ID,锁不会升级,仍然为偏向锁
  • 竞争失败,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁

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

4.3 技术实现

一个synchronized方法被一个线程抢到了锁时,那这个方法所在的锁对象就会将其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入 Monitor 去竞争对象了。

偏向锁的操作不用直接捅到操作系统,不涉及用户到内核转换,不必要直接升级为最高级,我们以一个account对象的“对象头”为例,

在这里插入图片描述

假如有一个线程执行到synchronized代码块的时候,JVM使用CAS操作把线程指针ID记录到Mark Word当中,并修改偏向标示,标示当前线程获得该锁。锁对象变成偏向锁(通过CAS修攻对象头里的锁标志位》,字面意思是“偏向于第一个获得它的线程”的锁。执行完同步代码块后,线程并不会主动释放偏向锁。

在这里插入图片描述

这时线程获得了锁,可以执行同步代码块。当该线程第二次到达同步代码块时会判断此时持有锁的线程是否还是自己(持有锁的线程ID也在对象头里),JVM通过account对象的Mark Word判断,当前线程ID还在,说明还持有着这个对象的锁,就可以继续进入临界区工作。由于之前没有释放锁这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。

结论: JVM不用和操作系统协商设置Mutex(争取内核),它只需要记录下线程ID就标示自己获得了当前锁,不用操作系统接入。

上述就是偏向锁:在没有其他线程竞争的时候,一直偏向偏心当前线程,当前线程可以一直执行。

代码演示变化:需使用jol-core,演示版本如pom.xml

注意:默认偏向锁程序启动4s后才会开启,故程序编写睡眠了5s,也可通过设置vm参数修改

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.example</groupId><artifactId>TestPlus</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>TestMaven</artifactId><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version></dependency></dependencies></project>
public class MainMaven {public final static int SIZE = 10;public static void main(String[] args) throws InterruptedException {Object obj1 = new Object();System.out.println("obj1加锁前:" + ClassLayout.parseInstance(obj1).toPrintable());TimeUnit.SECONDS.sleep(5);System.out.println("obj1程序启动5s后:" + ClassLayout.parseInstance(obj1).toPrintable());Object obj2 = new Object();System.out.println("obj2加锁前:" + ClassLayout.parseInstance(obj2).toPrintable());synchronized (obj1){System.out.println("obj1加锁后:" + ClassLayout.parseInstance(obj1).toPrintable());}synchronized (obj2){System.out.println("obj2加锁后:" + ClassLayout.parseInstance(obj2).toPrintable());}}
}
obj1加锁前:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalobj1程序启动5s后:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalobj2加锁前:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalobj1加锁后:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           b8 f2 cf 24 (10111000 11110010 11001111 00100100) (617607864)4     4        (object header)                           7d 00 00 00 (01111101 00000000 00000000 00000000) (125)8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totalobj2加锁后:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 88 a8 71 (00000101 10001000 10101000 01110001) (1906870277)4     4        (object header)                           92 01 00 00 (10010010 00000001 00000000 00000000) (402)8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

根据演示结果可推得结论:

  • obj1加锁前和程序启动5s后偏向锁标志未变且obj2加锁前偏向锁开启-》偏向锁开启后创建的锁对象才具备偏向锁性质
  • obj1加锁前锁标识为 001,加锁后为00-》可由无锁状态直接升级为轻量锁
  • obj2创建时锁标识即 101,即偏向锁开启后创建的对象,偏向锁位默认为1,只是没有当前线程指针故其余比特为0

4.4 偏向锁的撤销

偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。徽销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行

  1. 第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级。此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁
  2. 第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向

4.5 题外话

因为维护成本高,Java15逐步废弃偏向锁

5. 轻量级锁

  • 轻量级锁:多线程竞争,但是任意时刻最多只有一个线程竞争,即不存在锁竞争太过激烈的情况,也就没有线程阻塞

  • 主要作用:轻量级锁是为了在线程近乎交替执行同步块时提高性能。有线程来参与锁的竞争,但是获取锁的冲突事件极短,本质就是自旋锁CAS

  • 主要目的:在没有多线程竞争的前提下,通过CAS减少重量级锁使用操作系统互斥量产生的性能消耗,说白了先自旋,升级时机:当关闭偏向锁功能或多线程竞争偏向锁会导致偏向锁升级为轻量级锁

假如线程A已经拿到锁,这时线程B又来抢该对象的锁,由于该对象的锁已经被线程A拿到,当前该锁已是偏向锁了。而线程B在争抢时发现对象头Mark Word中的线程ID不是线程B自己的线程ID(而是线程A),那线程B就会进行CAS操作希望能获得锁。

此时线程B操作中有两种情况:

  • 如果锁获取成功,直接替换Mark Word中的线程ID为B自己的ID(A -> B),重新偏向于其他线程(即将偏向锁交给其他线程,相当于当前线程“被”“释放了锁),该锁会保持偏向锁状态,A线程Over,B线程上位;
  • 如果锁获取失败,则偏向锁升级为轻量级锁(设置偏向锁标识为0并设置锁标志位为00)。此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程B会进入自旋等待获得该轻量级锁。

5.1 轻量级锁的加锁

JVM会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,官方称为Displaced Mark Word。若一个线程获得锁时发现是轻量级锁,会把锁的Mark Word复制到自己的Displaced Mark Word里面。然后线程尝试用CAS将锁的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示Mark Word已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁。

自旋CAS:不断尝试去获取锁,能不升级就不往上捅,尽量不要阻塞

5.2 轻量级锁的释放

在释放锁时,当前线程会使用 CAS操作将Displaced Mark Word的内容复制回锁的Mark Word里面。如果没有发生竞争,那么这个复制的操作会成功。如果有其他线程因为自旋多次导致轻量级锁升级成了重量级锁,那么CAS操作会失败,此时会释放锁并唤醒被阻寒的线程

5.3 锁升级

轻量锁自旋达到一定次数和程度时候就会升级到重量锁。

  • java6之前,默认情况下自旋次数是10次,或者自旋线程数超过CPU核数一半
  • java6之后,自适应自旋锁
    • 原理:线程如果自旋成功了,那下次自旋的最大次数会增加,因为JVM认为既然上次成功了,那么这一次也很大概率会成功;反之,如果很少会自旋成功,那么下次会减少自旋的次数甚至不自旋,避免CPU空转
    • 根据同一个锁上一次自旋的时间和拥有锁线程的状态来决定最大的自旋的次数

6. 重量级锁

Java中synchronized的重量级锁,是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始拉置插入monitor enter指令,在结束位置插入monitor exit指令。
当线程执行到monitor enter指今时,会尝试获取对象所对应的Monitor所有权,如果获取到了,即获取到了锁,会在Monitor的owner中存放当前线程的id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor

public class MainMaven {public final static int SIZE = 10;private static Object lockObj;public static void main(String[] args) throws InterruptedException {lockObj = new Object();System.out.println("lockObj加锁前:" + ClassLayout.parseInstance(lockObj).toPrintable());new Thread(() -> {for (int i = 0; i < SIZE; i++) {test();}}).start();new Thread(() -> {for (int i = 0; i < SIZE; i++) {test();}}).start();new Thread(() -> {for (int i = 0; i < SIZE; i++) {test();}}).start();}public static void test() {synchronized (lockObj) {System.out.println("lockObj加锁后:" + ClassLayout.parseInstance(lockObj).toPrintable());}}
}

输出:

lockObj加锁前:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     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)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totallockObj加锁后:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           80 f3 8f ce (10000000 11110011 10001111 11001110) (-829426816)4     4        (object header)                           1c 02 00 00 (00011100 00000010 00000000 00000000) (540)8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes totallockObj加锁后:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           3a a0 c6 77 (00111010 10100000 11000110 01110111) (2009505850)4     4        (object header)                           1c 02 00 00 (00011100 00000010 00000000 00000000) (540)8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total...lockObj加锁后:java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           3a a0 c6 77 (00111010 10100000 11000110 01110111) (2009505850)4     4        (object header)                           1c 02 00 00 (00011100 00000010 00000000 00000000) (540)8     4        (object header)                           e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

可以看到从无锁->轻量级锁->重量级锁的过程

7. 锁升级与hashCode

锁升级为轻量级或重量级锁后,Mark Word中保存的分别是线程栈里的锁记录指针重量级锁指针,已经没有位置再保存哈希码,GC年龄了,那么这些信息被移动到哪里去了呢?

  • 在无锁状态下,Mark Word中可以存储对象的identity hash code值。当对象的hashCode()方法第一次被调用时,JVM会生成对应的identity hash code值并将该值存储到Mark Word中
  • 对干偏向锁,在线程获取偏向锁时,会用Thread D和epoch值覆盖identity hash code所在的位置。如果一个对象的hashCode()方法已经被调用过一次之后,这个对象不能被设置偏向锁。因为如果可以的话,那Mark Word中的identity hash code必然会被偏向线程ld给覆盖,这就会造成同一个对象前后两次调用hashCode()方法得到的结果不一致
    • 当一个对象已经计算过identity hash code,它就无法进入偏向锁状态,跳过偏向锁,直接升级为轻量锁
    • 偏向锁过程中遇到一致性哈希计算请求,立马撤销偏向模式,膨胀为重量级锁
  • 升级为轻量级锁时,JVM会在当前线程的栈顺中创建一个锁记录(Lock Record)空间,用于存储锁对象的Mark Word拷贝,该拷贝中可以包含identity hash code,所以轻量级锁可以和identity hash code共存,哈希码和GC年龄自然保存在此,释放锁后会将这些信息写回到对象头
  • 升级为重量级锁后,Mark Word保存的重量级锁指针,代表重量级锁的ObiectMonitor类里有字段记录非加锁状态下的Mark Word,锁释放后也会将信息写回到对象头

默认情况下,偏向锁开启后,创建的对象的锁状态是010即偏向锁状态,但是无线程指针,此时若调用hashCode()方法,对象会退回无锁状态001

8. 总结

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块场景
轻量级锁竞争的线程不会阻塞,提高了程序的响应速度如果始终得不到锁竞争的线程,适用自旋会消耗CPU追求响应速度,同步块执行速度非常快
重量级锁线程竞争不使用自旋,不会消耗CPU线程阻塞,响应时间缓慢追求吞吐量,同步块执行速度较长

synchronized锁升级过程总结: 一句话,就是先自旋,不行再阻塞。实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式

synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁

9. JIT编译器对锁的优化

JIT,Just In Time Compiler,一般译为即时编译器

9.1 锁消除

逃逸分析-锁消除

sb是方法中的局部对象,就只在该方法内的作用域有效,不同线程调用该方法若使用锁同步机制则是白白浪费资源。故JIT在编译阶段会优化该处,进行锁的消除

默认开启:

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

其中+DoEscapeAnalysis表示开启逃逸分析,+EliminateLocks表示锁消除。

public class Main {public static void main(String[] args) {long start = System.currentTimeMillis();int size = 100000000;for (int i = 0; i < size; i++) {createStringBuffer("JVM", "锁消除");}long timeCost = System.currentTimeMillis() - start;System.out.println("createStringBuffer:" + timeCost + " ms");}public static String createStringBuffer(String str1, String str2) {StringBuffer sb = new StringBuffer();sb.append(str1);// append方法是同步操作sb.append(str2);return sb.toString();}}

默认启动

createStringBuffer:2377 ms

关闭锁消除后

createStringBuffer:3933 ms

9.2 锁粗化

假如方法中首尾相接,前后相邻的都是同一锁对象,那JIT编译器就会把这几个synchronized块合并为一个大块,加粗加大范围,一次申请锁即可,避免次次的加锁和释放锁,提升了性能

public class Main {public static void main(String[] args) {synchronized (Main.class) {System.out.println(1);}synchronized (Main.class) {System.out.println(2);}synchronized (Main.class) {System.out.println(3);}synchronized (Main.class) {System.out.println(1);System.out.println(2);System.out.println(3);}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/112252.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springweb+vue前后端分离开发,集成部署

背景&#xff1a; 在自己做测试的时候&#xff0c;由于需要项目和项目的前端页面使用同样接口访问&#xff0c;所以需要将前端代码部署到后端项目下。前端采用vue&#xff0c;后端采用springboot。 首先时建立一个vue项目&#xff0c;这个可以参照网上的案例&#xff0c;创建方…

Node.js的安装

直接在浏览器中搜索Node.js即可 打开下载好的文件 验证是否安装成功 在cmd中输入 node -v&#xff0c;若结果为版本号那就是成功的 环境配置 配置全局模块所在的路径缓存cache的路径 在安装目录中新建两个文件夹&#xff0c;文件夹名为:node_cache和node_global 输…

C++之this指针

前言 C中对象模型和this指针是面向对象编程中的重要概念。对象模型描述了对象在内存中的布局和行为&#xff0c;包括成员变量、成员函数的存储方式和访问权限。this指针是一个隐含的指针&#xff0c;指向当前对象的地址&#xff0c;用于在成员函数中引用当前对象的成员变量和成…

搭建伪分布式Hadoop

文章目录 一、Hadoop部署模式&#xff08;一&#xff09;独立模式&#xff08;二&#xff09;伪分布式模式&#xff08;三&#xff09;完全分布式模式 二、搭建伪分布式Hadoop&#xff08;一&#xff09;登录虚拟机&#xff08;二&#xff09;上传安装包&#xff08;三&#xf…

VMware Workstation里面安装ubuntu20.04的流程

文章目录 前言一、获取 desktop ubuntu20.04 安装镜像二、VMware Workstation下安装ubuntu20.041. VMware Workstation 创建一个新的虚拟机2. ubuntu20.04的安装过程3. 登录ubuntu20.044. 移除 ubuntu20.04 安装镜像总结参考资料前言 本文主要介绍如何在PC上的虚拟机(VMware W…

WordPress SMTP邮件发送插件 Easy WP SMTP

Easy WP SMTP是一款 WordPress 邮件发送插件&#xff0c;WordPress 中经常用到邮件发送&#xff0c;包括新注册用户的邮件通知、找回密码通知、评论回复通知等。因为云服务器默认不启用 SMTP功能&#xff0c;所以需要安装 SMTP插件来解决这个问题。 SMTP 主机&#xff1a;smtp.…

javascript/python 笔记: folium feature group自动切换

1 python部分 python部分只能是静态的结果 1.1 导入库 import folium import math 1.2 数据 cell_lst表示基站位置&#xff0c;location_lst表示 用户实际位置&#xff08;均为伪数据&#xff09; cell_lst[[1.341505, 103.682498],[1.342751, 103.679604],[1.341505, 10…

YCSB and TPC-C on MySQL(避免重复load)

一、编译安装MySQL 下载mysql5.7.28源码 https://downloads.mysql.com/archives/community/ Select Operating System 选择 Source Code Select OS version 选择 All Operating Systems 选择带有boost的版本 安装系统包 apt -y install make cmake gcc g perl bison libai…

虹科分享 | 赋能物流机器人:CANopen通信如何发挥重要作用?

现代物流领域迅速融入了技术进步&#xff0c;特别是随着自主机器人的兴起&#xff0c;这一趋势越发明显。确保这些机器人在复杂的仓库环境中精确运行的一个关键方面是CANopen通信协议。该协议集成了各种组件&#xff08;电机、传感器、摄像头和先进的电池系统&#xff09;&…

vue视频直接播放rtsp流;vue视频延迟问题解决;webRTC占cpu太大卡死问题解决;解决webRTC播放卡花屏问题:

播放多个视频 <div class"video-box"><div class"video"><iframe style"width:100%;height:100%;" name"ddddd" id"iframes" scrolling"auto" :src"videoLeftUrl"></iframe>&l…

轴承寿命相关细节的研究

数据集PHM2012 介绍一下IEEE PHM2012数据集_phm2012轴承数据集-CSDN博客 标签如何设置的? 剩余寿命预测的标签设置_rul 标签_兔子牙丫丫的博客-CSDN博客 参考自刘硕师兄的毕业答辩PPT 图 4.9 训练数据的切分方法 数据段的重叠切分&#xff0c;不仅可以覆盖更多的标签数据…

任务调度框架-如何实现定时任务+RabbitMQ事务+手动ACK

任务调度框架 Java中如何实现定时任务&#xff1f; 比如&#xff1a; 1.每天早上6点定时执行 2.每月最后一个工作日&#xff0c;考勤统计 3.每个月25号信用卡还款 4.会员生日祝福 5.每隔3秒&#xff0c;自动提醒 10分钟的超时订单的自动取消&#xff0c;每隔30秒或1分钟查询…

Redis在分布式场景下的应用

分布式缓存 缓存的基本作用是在高并发场景下对应服务的保护缓冲 – 基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题&#xff1a; redis由于高强度性能采用内存 但是意味着丢失的风险单结点redis并发能力有限分布式服务中数据过多 依赖内存的redis 明显单机不…

微信小程序自定义组件及投票管理与个人中心界面搭建

14天阅读挑战赛 人生本来就没定义&#xff0c;任何的价值都是自己赋予。 目录 一、自定义tabs组件 1.1 创建自定义组件 1.2 tabs.wxml 编写组件界面 1.3 tabs.wxss 设计样式 1.4 tabs.js 定义组件的属性及事件 二、自定义组件使用 2.1 引用组件 2.2 编写会议界面内容 …

DTI综述(更新中)

Deep Learning for drug repurposing&#xff1a;methods&#xff0c;datasets&#xff0c;and applications 综述读完&#xff0c;觉得少了点东西&#xff0c;自己写个DTI综述 Databases(包括但不限于文章中的) DATABASEDESCRIBEBindingDB有详细的drug信息和对应的target&a…

推荐《中华小当家》

《中华小当家&#xff01;》 [1] 是日本漫画家小川悦司创作的漫画。该作品于1995年至1999年在日本周刊少年Magazine上连载。作品亦改编为同名电视动画&#xff0c;并于1997年发行播出。 时隔20年推出续作《中华小当家&#xff01;极》&#xff0c;于2017年11月17日开始连载。…

简单秒表设计仿真verilog跑表,源码/视频

名称&#xff1a;简单秒表设计仿真 软件&#xff1a;Quartus 语言&#xff1a;Verilog 代码功能&#xff1a; 秒表显示最低计时为10ms&#xff0c;最大为59:99&#xff0c;超出返回00&#xff1a;00 具有复位、启动、暂停三个按键 四个数码管分别显示4个时间数字。 演示…

LCR 177. 撞色搭配

LCR 177. 撞色搭配 LCR 177. 撞色搭配 迷你游戏之寻找两个单身狗 int* sockCollocation(int* sockets, int socketsSize, int* returnSize) {int* arr (int*)malloc(2 * sizeof(int));int ret 0;for (int i 0; i < socketsSize; i){ret ^ sockets[i];}int pos 0;for…

【七:docken+jenkens部署】

一&#xff1a;腾讯云轻量服务器docker部署Jenkins https://blog.csdn.net/qq_35402057/article/details/123589493 步骤1&#xff1a;查询jenkins版本&#xff1a;docker search jenkins步骤2&#xff1a;拉取jenkins镜像 docker pull jenkins/jenkins:lts步骤3&#xff1a;…

python -pandas -处理excel合并单元格问题

对于合并的单元格&#xff0c;不进行处理情况下&#xff0c;会默认输出nan问题 解决方法&#xff1a; class A(object):def __init__(self, xlsx_file_path, sheet_index):self.xlsx_file FileDataProcesser.read_excel(xlsx_file_path, sheet_index)self.sheet_data self.…