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 的使用(二)
786 0
Linux应用开发基础知识——Makefile 的使用(二)
|
SQL 关系型数据库 MySQL
MySQL数据库,可以使用二进制日志(binary log)进行时间点恢复
对于MySQL数据库,可以使用二进制日志(binary log)进行时间点恢复。二进制日志是MySQL中记录所有数据库更改操作的日志文件。要进行时间点恢复,您需要执行以下步骤: 1. 确保MySQL配置文件中启用了二进制日志功能。在配置文件(通常是my.cnf或my.ini)中找到以下行,并确保没有被注释掉: Copy code log_bin = /path/to/binary/log/file 2. 在需要进行恢复的时间点之前创建一个数据库备份。这将作为恢复的基准。 3. 找到您要恢复到的时间点的二进制日志文件和位置。可以通过执行以下命令来查看当前的二进制日志文件和位
1063 1
|
监控 Linux 开发者
Docker服务systemd配置文件详解
Docker服务systemd配置文件详解
822 0
|
Java Maven 微服务
IDEA 2021 Spring Cloud 项目搭建 步骤演示 图文解说 (基础版)(一)
IDEA 2021 Spring Cloud 项目搭建 步骤演示 图文解说 (基础版)
976 0
IDEA 2021 Spring Cloud 项目搭建 步骤演示 图文解说 (基础版)(一)
|
12月前
|
存储 资源调度 Cloud Native
VMware vCenter Server 8.0U3e 新增功能简介
VMware vCenter Server 8.0U3e 新增功能简介
442 4
VMware vCenter Server 8.0U3e 新增功能简介
|
自然语言处理 JavaScript 数据管理
HarmonyOS Next 实战卡片开发 01
本文详细介绍了 HarmonyOS Next 中的卡片开发,涵盖基本概念、类型、创建、配置、能力支持、生命周期及通信等内容。Form Kit 提供将应用重要信息前置到服务卡片的功能,减少跳转层级,适用于嵌入系统应用(如桌面),支持拉起页面与发送消息等交互。卡片分为静态与动态两种类型,分别适用于不同刷新需求场景。
370 0
HarmonyOS Next 实战卡片开发 01
|
JavaScript
componentDidUpdate 方法在组件更新后做什么?
【10月更文挑战第27天】在 `componentDidUpdate` 中使用 `this.props` 和 `this.state` 时要小心,因为此时它们已经是更新后的最新值,与 `prevProps` 和 `prevState` 所代表的前一个状态不同。同时,如果在 `componentDidUpdate` 中再次调用 `setState`,要确保不会导致无限循环的更新,通常需要添加适当的条件判断来避免不必要的状态更新。
395 2
|
负载均衡 网络协议 算法
Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式
本文探讨了Docker容器环境中服务发现与负载均衡的技术与方法,涵盖环境变量、DNS、集中式服务发现系统等方式,以及软件负载均衡器、云服务负载均衡、容器编排工具等实现手段,强调两者结合的重要性及面临挑战的应对措施。
507 4
|
机器学习/深度学习 数据可视化 数据挖掘
Jupyter Notebook: 数据科学的最佳实践
【8月更文第29天】Jupyter Notebook 是一个交互式的计算环境,非常适合进行数据分析、可视化和机器学习任务。它不仅支持多种编程语言(如 Python、R 和 Julia),还能将文本、代码、图表和多媒体元素融合在一起,非常适合编写可重复使用的分析报告。
926 1
|
安全 Java
java系列知识之~SimpleDateFormat日期格式化
这篇文章介绍了Java中`SimpleDateFormat`类的使用,包括如何创建实例、格式化日期对象为字符串、解析字符串为日期对象,并提供了常见日期模式和使用示例,同时指出了`SimpleDateFormat`不是线程安全及其它一些注意事项。

热门文章

最新文章