并发编程——synchronized优化原理

耶瞳 耶瞳     2023-03-08     295

关键词:

如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:耶瞳空间

一:基本概念

使用synchronized实现线程同步,即加锁,实现的是悲观锁。加锁可以使一段代码在同一时间只有一个线程可以访问,在增加安全性的同时,牺牲掉的是程序的执行性能。

为了在一定程度上减少获得锁和释放锁带来的性能消耗,在jdk6之后便引入了“偏向锁”和“轻量级锁”,所以Java中的锁总共有4种锁状态,级别由低到高依次为:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

锁竞争:如果多个线程轮流获取一个锁,但是每次获取锁的时候都很顺利,没有发生阻塞,那么就不存在锁竞争。只有当某线程尝试获取锁的时候,发现该锁已经被占用,只能等待其释放,这才发生了锁竞争。

二:无锁

无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

无锁的特点是修改操作会在循环内进行,线程会不断的尝试修改共享资源。如果没有冲突就修改成功并退出,否则就会继续循环尝试。如果有多个线程修改同一个值,必定会有一个线程能修改成功,而其他修改失败的线程会不断重试直到修改成功。

三:偏向锁

偏向锁,顾名思义,它会偏向于第一个访问锁的线程。如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。线程第二次到达同步代码块时,会判断此时持有锁的线程是否就是自己,如果是则正常往下执行。由于之前没有释放锁,这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。

偏向锁的获取流程:

  • 查看Mark Word中偏向锁的标识以及锁标志位,若是否偏向锁为1且锁标志位为01,则该锁为可偏向状态。
  • 若为可偏向状态,则测试Mark Word中的线程ID是否与当前线程相同,若相同,则直接执行同步代码,否则进入下一步。
  • 当前线程通过CAS操作竞争锁,若竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行同步代码,若竞争失败,进入下一步。
  • 当前线程通过CAS竞争锁失败的情况下,说明有竞争。当到达全局安全点时之前获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。

如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会释放它的偏向锁,将锁恢复到标准的轻量级锁。释放偏向锁的时候会导致STW(stop the word)操作。

偏向锁的释放流程:偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁状态的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销需要等待全局安全点(即没有字节码正在执行),它会暂停拥有偏向锁的线程,撤销后偏向锁恢复到未锁定状态或轻量级锁状态。

四:轻量级锁

在轻量级锁状态下继续锁竞争,没有抢到锁的线程将自旋,即不停地循环判断锁是否能够被成功获取。长时间的自旋操作是非常消耗资源的,一个线程持有锁,其他线程就只能在原地空耗CPU,执行不了任何有效的任务,这种现象叫做忙等(busy-waiting)。如果锁竞争情况严重,某个达到最大自旋次数的线程,会将轻量级锁升级为重量级锁。

锁自旋:如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

轻量级锁的加锁过程:

  1. 当线程执行代码进入同步块时,若Mark Word为无锁状态,虚拟机先在当前线程的栈帧中建立一个名为Lock Record的空间,用于存储当前对象的Mark Word的拷贝,官方称之为“Dispalced Mark Word”。
  2. 复制对象头中的Mark Word到锁记录中。
  3. 复制成功后,虚拟机将用CAS操作将对象的Mark Word更新为执行Lock Record的指针,并将Lock Record里的owner指针指向对象的Mark Word。如果更新成功,则执行4,否则执行5。
  4. 如果更新成功,则这个线程拥有了这个锁,并将锁标志设为00,表示处于轻量级锁状态。
  5. 如果更新失败,虚拟机会检查对象的Mark Word是否指向当前线程的栈帧,如果是则说明当前线程已经拥有这个锁,可进入执行同步代码。否则说明多个线程竞争,轻量级锁就会膨胀为重量级锁,Mark Word中存储重量级锁(互斥锁)的指针,后面等待锁的线程也要进入阻塞状态。

五:重量级锁

当线程尝试获取锁时,发现被占用的锁是重量级锁,则直接将自己挂起,等待将来被唤醒。在JDK1.6之前,synchronized会直接加重量级锁,很明显现在得到了很好的优化。

重量级锁的特点:其他线程试图获取锁时,都会被阻塞,只有持有锁的线程释放锁之后才会唤醒这些线程。

六:总结

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

java并发编程:核心理论

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

并发编程系列之synchronized实现原理(代码片段)

并发编程系列之Synchronized实现原理1、了解synchronized字节码下面给出一个简单例子,synchronized关键字加在两个方法上,另外一个加在方法里publicclassSynchroinzedDemostaticinta;publicstaticsynchronizedvoidadd1(intb)a+=b;< 查看详情

并发编程之synchronized------jvm对synchronized的优化(代码片段)

一、锁的粗化看如下代码publicclassTestStringBufferstb=newStringBuffer();publicvoidtest1()//jvm的优化,锁的粗化stb.append("1");stb.append("2");stb.append("3");stb.append("4");首先我们要清除StringBuffer是线程安全的,因为它在每一个方法上都加了sync 查看详情

java并发编程:synchronized及其实现原理

一、synchronized的基本使用  synchronized是Java中解决并发问题的一种最常用的方法,也就最简单的一种方法。synchronized的作用有以下三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能及时可见(3)有效解决重... 查看详情

java并发编程:synchronized及其实现原理

一、Synchronized的基本使用  Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchronized的作用主要有三个:(1)确保线程互斥的访问同步代码(2)保证共享变量的修改能够及时可见(3)有效解决... 查看详情

java高并发编程实战4,synchronized与lock底层原理(代码片段)

目录一、synchronized底层原理二、反编译synchronized方法1、定义一个最简单的synchronized方法2、通过```javap-cSynchronizedTest.class```进行反编译:3、代码分析三、偏向锁四、Lock源码分析1、Lock锁的方法如下2、下面分... 查看详情

java高并发编程实战4,synchronized与lock底层原理(代码片段)

目录一、synchronized底层原理二、反编译synchronized方法1、定义一个最简单的synchronized方法2、通过```javap-cSynchronizedTest.class```进行反编译:3、代码分析三、偏向锁四、Lock源码分析1、Lock锁的方法如下2、下面分... 查看详情

并发编程——synchronized优化原理

...我的个人网站看看:耶瞳空间一:基本概念使用synchronized实现线程同步,即加锁,实现的是悲观锁。加锁可以使一段代码在同一时间只有一个线程可以访问,在增加安全性的同时,牺牲掉的是程序的执行性... 查看详情

juc并发编程--synchronized原理进阶之轻量级锁&锁膨胀(代码片段)

...来优化。轻量级锁对使用者是透明的,即语法仍然是synchronized(也就是在给对象加锁的时候,优先考虑使用轻量级锁去加锁,如果轻量级级锁加锁失败, 查看详情

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

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

java高并发与多线程;:synchronized关键字的实现原理

synchronzied关键字应该是Java并发编程中最重要的内容了,甚至没有之一。在JDK6以前,synchronized关键字还代表着一把重量级锁,因此在JUC包里还推出了Lock类来替代synchronized,不过JDK6以后的synchronized经过优化,引入了偏向锁、轻量... 查看详情

jmmsynchronized原理可见性有序性happens-beforecas(无锁并发)原子性synchronized的优化(代码片段)

目录JMMsynchronized原理可见性有序性happens-beforeCAS(无锁并发)原子性synchronized的优化轻量级锁重量级锁的优化偏向锁(轻量级锁的优化)其他优化JMMJavaMemoryModel(java内存模型)定义了一套多线程读写共享数... 查看详情

java并发深入分析volatile的实现原理

通过前面一章我们了解了synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized。如果一个变量使用volatile,则它比使用synchronized的成本更加低,因为它不会引起线程上下文的切换和... 查看详情

并发编程面试(代码片段)

1.说说synchronized关键字的底层原理是什么?synchronized底层的原理,是跟jvm指令和monitor有关系的如果我们用到了synchronized关键字,在底层编译后的JVM指令中,会有monitorenter和monitorexit两个指令加锁执行monitorenter指令,释放锁执行moni... 查看详情

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

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

彻底理解java并发编程之synchronized关键字实现原理剖析(代码片段)

...大家好,我是小于哥,今天分享一个关于Java面试Synchronized的一个问题:怎么理解Synchronized的关键字,如果你只回答只是一把锁,保证线程的同步,那么你基本上面试要挂的,昨天竟然有知友被问倒了~~... 查看详情

彻底理解java并发编程之synchronized关键字实现原理剖析(代码片段)

...大家好,我是小于哥,今天分享一个关于Java面试Synchronized的一个问题:怎么理解Synchronized的关键字,如果你只回答只是一把锁,保证线程的同步,那么你基本上面试要挂的,昨天竟然有知友被问倒了~~... 查看详情

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

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