synchronized原理

简介: synchronized原理

4e33ae1c68c51a0a3dbc2deb7e141702.png

一.锁升级

synchronized相对比较‘智能’,会根据锁竞争的激烈程度进行锁升级,其锁升级策略如下:


8f42c1513beb13652cfa335a3fe241e3.png

我们一一对一下四种上锁情况进行分析:

无锁:锁对象没有锁竞争时,synchronized并不会进行上锁操作,无锁状态是理论上会存在的状态,但是在实际业务场景中几乎不存在

偏向锁:偏向锁并不是真正的加锁,而是对锁对象进行加‘标签’的操作,这个标签记录哪个线程调用了这把锁,如果后续没有其他线程来调用这把锁,就一直保持偏向锁的状态,如果后面有其他线程同样竞争这把锁,那么只需要将标签中的线程与该线程进行对比,如果不一致,则有偏向锁转化为轻量级锁。偏向锁在本质上是延迟加锁的操作:能不加锁就不加锁,尽量避免不必要的加锁开销。

轻量级锁:当有其他线程对这把锁进行锁竞争,偏向锁的锁状态就会被解除,进入轻量级锁,在这里轻量级锁是自适应的自旋锁(通过CAS来实现):通过CAS检查并更新所资源的调用情况,如果更新成功,则加锁成功,更新失败认为锁被占用,不断进行自旋等待(并不放弃cpu),但是不断的自旋是会消耗内存,一般自旋一定的次数和时间就不再自旋了,这就是所谓的‘自适应’。

重量级锁:当锁竞争进一步加强时,轻量级锁就会转化为重量级锁,而重量级锁获取锁的过程如下:首先从用户态进入内核态,判断锁资源是否被占用,没有被占用则进行加锁操作,加锁成功后返回用户态,如果被占用该线程就进入锁的等待队列,当锁资源被释放之后,经过一些列沧海桑田(不及时),这时操作系统才考虑到被挂起的线程,这时才将该线程唤醒,进行加锁操作。

我们举生活中的一个例子对这四种锁状态进行说明:

a8ece6361d65c78b5beadce822eb4244.png

我们通过代码对四种锁状态进行解释

import org.openjdk.jol.info.ClassLayout;

 

public class Demo_404_Layout {

   // 定义一些变量

   private int count;

   private long count1 = 200;

   private String hello = "";

   // 定义一个对象变量

   private TestLayout test001 = new TestLayout();

 

   public static void main(String[] args) throws InterruptedException {

       // 创建一个对象的实例

       Object obj = new Object();

       // 打印实例布局

       System.out.println("=== 任意Object对象布局,起初为无锁状态");

       System.out.println(ClassLayout.parseInstance(obj).toPrintable());

       synchronized (obj) {

           System.out.println("==== 第一层synchronized加锁后");

           System.out.println(ClassLayout.parseInstance(obj).toPrintable());

 

           System.out.println("=== 延时4S开启偏向锁");

           // 延时4S开启偏向锁

           Thread.sleep(5000);

           // 创建本类的实例

           Demo_404_Layout monitor = new Demo_404_Layout();

           // 打印实例布局,注意查看锁状态为偏向锁

           System.out.println("=== 打印实例布局,注意查看锁状态为偏向锁");

           System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

 

           System.out.println("==== synchronized加锁");

           // 加锁后观察加锁信息

           synchronized (monitor) {

               System.out.println("==== 第一层synchronized加锁后");

               System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

               // 锁重入,查看锁信息

               synchronized (monitor) {

                   System.out.println("==== 第二层synchronized加锁后,锁重入");

                   System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

               }

               // 释放里层的锁

               System.out.println("==== 释放内层锁后");

               System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

           }

           // 释放所有锁之后

           System.out.println("==== 释放 所有锁");

           System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

 

           System.out.println("==== 多个线程参与锁竞争,观察锁状态");

           Thread thread1 = new Thread(() -> {

               synchronized (monitor) {

                   System.out.println("=== 在线程A 中获取锁,参与锁竞争,当前只有线程A 竞争锁,轻度锁竞争");

                   System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

               }

           });

           thread1.start();

 

           // 休眠一会,不与线程A 激烈竞争

           Thread.sleep(100);

           Thread thread2 = new Thread(() -> {

               synchronized (monitor) {

                   System.out.println("=== 在线程B 中获取锁,与其他线程进行锁竞争");

                   System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

               }

           });

           thread2.start();

 

           // 不休眠直接竞争锁,产生激烈竞争

           System.out.println("==== 不休眠直接竞争锁,产生激烈竞争");

           synchronized (monitor) {

               // 加锁后的类对象

               System.out.println("==== 与线程B 产生激烈的锁竞争,观察锁状态为fat lock");

               System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

           }

           // 休眠一会释放锁后

           Thread.sleep(100);

           System.out.println("==== 释放锁后");

           System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

 

           System.out.println("===========================================================================================");

           System.out.println("===========================================================================================");

           System.out.println("===========================================================================================");

           System.out.println("===========================================================================================");

           System.out.println("===========================================================================================");

           System.out.println("===========================================================================================");

 

           // 调用hashCode后才保存hashCode的值

           monitor.hashCode();

           // 调用hashCode后观察现象

           System.out.println("==== 调用hashCode后查看hashCode的值");

           System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

           // 强制执行垃圾回收

           System.gc();

           // 观察GC计数

           System.out.println("==== 调用GC后查看age的值");

           System.out.println(ClassLayout.parseInstance(monitor).toPrintable());

           // 打印类布局,注意调用的方法不同

           System.out.println("==== 查看类布局");

           System.out.println(ClassLayout.parseClass(Demo_404_Layout.class).toPrintable());

           // 打印类对象布局

           System.out.println("==== 查看类对象布局");

           System.out.println(ClassLayout.parseInstance(Demo_404_Layout.class).toPrintable());

 

 

       }

   }

}

cb4887119c74fb1301244fa461863fd8.png

e03845a13014e76a58718e22b1c7e24d.png

720dea4b895ad2461baad35a270e6c4c.png

dd1e4d440886469d64df9c29ec99d212.png

a5187dcdf9acb8df37c67c1706a85043.png

06b18decff4ae7d1a0f09b854ca35fcf.png

aa3cdc1254c153a4b15c7ace226b9a75.png


7812f99980e288089f6f59258c4438be.png

二.锁消除

锁消除是synchronized的一种优化策略,是编译器+JVM判断锁是否能够消除,如果能够消除就直接消除了

比如以下场景:当我们对变量进行修改时,将修改的代码块加上了synchronized,但是是在单线程的情况下,不存在线程安全问题,这时JVM直接将锁消除了

需要注意的是:只有JVM百分百确定不存在线程安全问题时,才会执行锁消除

另外一种场景:虽然JVM没办法决定在哪个地方加锁(程序员决定),但是如果是在多线程情况下的读操作,就不存在线程安全问题,所以这JVM直接将锁消除了


三.锁粗化

当在一段代码中出现多次加锁和释放锁的操作,这时候JVM会对锁自动进行锁的粗化,避免了锁的多次获取和释放


21b223d6cab3ce3fbc52d6753978e4ab.png

相关文章
|
Linux 编译器 C语言
Linux应用开发基础知识——Makefile 的使用(二)
Linux应用开发基础知识——Makefile 的使用(二)
647 0
Linux应用开发基础知识——Makefile 的使用(二)
|
JavaScript 前端开发 API
轻松搞定Vue3+Pinia-4-多个store
轻松搞定Vue3+Pinia-4-多个store
728 0
|
资源调度 Ubuntu JavaScript
从零开始在一个Ubuntu服务器上部署node后端
从零开始在一个Ubuntu服务器上部署node后端
436 0
openpyxl 一行代码批量修改单元格属性、修改全部单元格属性 、设置自动换行、修改全部单元格style为自动换行
openpyxl 一行代码批量修改单元格属性、修改全部单元格属性 、设置自动换行、修改全部单元格style为自动换行
442 0
|
11月前
|
JavaScript
componentDidUpdate 方法在组件更新后做什么?
【10月更文挑战第27天】在 `componentDidUpdate` 中使用 `this.props` 和 `this.state` 时要小心,因为此时它们已经是更新后的最新值,与 `prevProps` 和 `prevState` 所代表的前一个状态不同。同时,如果在 `componentDidUpdate` 中再次调用 `setState`,要确保不会导致无限循环的更新,通常需要添加适当的条件判断来避免不必要的状态更新。
279 2
|
11月前
|
运维 监控 安全
云计算环境下的运维挑战与解决方案
本文探讨了云计算环境中运维面临的主要挑战,包括资源管理、自动化部署、安全性问题等,并提出了相应的解决策略。通过案例分析和最佳实践,为云环境下的运维工作提供了指导和参考。
486 1
|
SQL 存储 DataWorks
Flink Forward Asia 2023 主会场精彩回顾
2023 年 12 月 8 日-9 日, **Flink Forward Asia(FFA)**峰会成功举行。Flink Forward Asia 是由 Apache 软件基金会官方授权、由阿里云承办的技术峰会。
16564 0
Flink Forward Asia 2023 主会场精彩回顾
|
机器学习/深度学习 自然语言处理 UED
上海科大等开源创新模型:文本生成精美3D服装
【9月更文挑战第4天】上海科技大学等机构近期开源了DressCode模型,可根据文本描述生成精美的3D服装,相关成果已发表于论文《DressCode: Autoregressively Sewing and Generating Garments from Text Guidance》。DressCode采用SewingGPT架构,结合GPT与交叉注意力机制,生成与文本匹配的缝纫模式,并利用预训练的稳定扩散模型生成逼真的PBR纹理。这不仅简化了服装设计流程,还提升了虚拟试穿和数字人创建的可能性。研究显示,DressCode在服装质量和一致性方面超越现有技术,具有广泛应用潜力。
246 2
|
Linux Perl
在Linux中,如何停止正在运行的进程?
在Linux中,如何停止正在运行的进程?
|
应用服务中间件 API 网络安全
运维笔记:宿主机转发实现多容器复用CA证书
运维笔记:宿主机转发实现多容器复用CA证书
189 4