Java-synchronized实现详解(从Java到汇编)

news2024/11/29 4:39:59

synchronized作为java语言中的并发关键词,其在代码中出现的频率相当高频,大多数开发者在涉及到并发场景时,一般都会下意识得选取synchronized。

synchronized在代码中主要有三类用法,根据其用法不同,所获取的锁对象也不同,如下所示:

  • 修饰代码块:这种用法通常叫做同步代码块,获取的锁对象是在synchronized中显式指定的
  • 修饰实例方法:这种用法通常叫做同步方法,获取的锁对象是当前的类对象
  • 修饰静态方法:这种用法通常叫做静态同步方法,获取的锁对象是当前类的类对象

下面我们一起来测试下三种方式下,对象锁的归属及锁升级过程,SynchronizedTestClass类代码如下:

import org.openjdk.jol.info.ClassLayout;

public class SynchronizedTestClass {
    private Object mLock = new Object();
    public void testSynchronizedBlock(){
        System.out.println("before get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(mLock).toPrintable());
        synchronized (mLock) {
            System.out.println("testSynchronizedBlock start:"+Thread.currentThread().getName());
            System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(mLock).toPrintable());
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("testSynchronizedBlock end:"+Thread.currentThread().getName());
        }
    }

    public synchronized void testSynchronizedMethod() {
        System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(this).toPrintable());
        System.out.println("testSynchronizedMethod start:"+Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("testSynchronizedMethod end:"+Thread.currentThread().getName());
    }

    public static synchronized void testSynchronizedStaticMethod() {
        System.out.println("after get Lock in thread:"+Thread.currentThread().getName()+">>>"+ ClassLayout.parseInstance(SynchronizedTestClass.class).toPrintable());
        System.out.println("testSynchronizedStaticMethod start:"+Thread.currentThread().getName());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("testSynchronizedStaticMethod end:"+Thread.currentThread().getName());
    }
}

同步代码块

在main函数编写如下代码,调用SynchronizedTestClass类中包含同步代码块的测试方法,如下所示:

public static void main(String[] args) {
    SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();
    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });
}

运行结果如下:

1-4-10-1

从上图可以看出在线程2获取锁前,mLock处于无锁状态,等线程2获取锁后,mLock对象升级为轻量级锁,等线程1获取锁后升级为重量级锁,有同学要问了,你在多线程与锁中不是说了synchronized锁升级有四个吗?你是不是写BUG了,当然没有啊,现在我们来看看偏向锁去哪儿了?

偏向锁

对于不同版本的JDK而言,其针对偏向锁的开关和配置均有所不同,我们可以通过执行java -XX:+PrintFlagsFinal -version | grep BiasedLocking来获取偏向锁相关配置,执行命令输出如下:

1-4-10-2

从上图可以看出在JDK 1.8上,偏向锁默认开启,具有4秒延时,那么我们修改main内容,延时5秒开始执行,看看现象如何,代码如下:

public static void main(String[] args) {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();

    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });

    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });
}

输出如下:

1-4-10-3

从上图可以看出在延迟5s执行后,mLock锁变成了无锁可偏向状态,结合上面两个示例,我们可以看出,在轻量级锁和偏向锁阶段均有可能直接升级成重量级锁,是否升级依赖于当时的锁竞争关系,据此我们可以得到synchronized锁升级的常见过程,如下图所示:

synchronized

可以看出,我们遇到的两种情况分别对应升级路线1和升级路线4。

同步方法

使用线程池调用SynchronizedTestClass类中的同步方法,代码如下:

public static void main(String[] args) {
    SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();

    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedMethod();
        }
    });

    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedMethod();
        }
    });
}

运行结果如下:

1-4-10-4

可以看出,在调用同步方法时,直接升级为重量级锁,同一时刻,有且仅有一个线程在同步方法中执行,其他函数在同步方法入口处阻塞等待。

静态同步方法

使用线程池调用SynchronizedTestClass类中的静态同步方法,代码如下

    public static void main(String[] args) {
        ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
        testSynchronizedBlock.execute(new Runnable() {
            @Override
            public void run() {
                SynchronizedTestClass.testSynchronizedStaticMethod();
            }
        });
        testSynchronizedBlock.execute(new Runnable() {
            @Override
            public void run() {
                SynchronizedTestClass.testSynchronizedStaticMethod();
            }
        });
    }

运行结果如下:

1-4-10-5

可以看出,在调用静态同步方法时,直接升级为重量级锁,同一时刻,有且仅有一个线程在静态同步方法中执行,其他函数在同步方法入口处阻塞等待。

前面我们看的是多个线程竞争同一个锁对象,那么假设我们有三个线程分别执行这三个函数,又会怎样呢?代码如下:

public static void main(String[] args) {
    SynchronizedTestClass synchronizedTestClass = new SynchronizedTestClass();

    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            SynchronizedTestClass.testSynchronizedStaticMethod();
        }
    });
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedMethod();
        }
    });
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            synchronizedTestClass.testSynchronizedBlock();
        }
    });
}

运行结果:

1-4-10-10

可以看到,3个线程各自运行,互不影响,这也进一步印证了前文所说的锁对象以及MarkWord中标记锁状态的概念。

synchronized实现原理

上面已经学习了synchronized的常见用法,关联的锁对象以及锁升级的过程,接下来我们来看下synchronized实现原理,仍然以上面的SynchronizedTestClass为例,查看其生成的字节码来了解synchronized关键字的实现。

同步代码块

testSynchronizedBlock其所对应的字节码如下图所示:

1-4-10-6

从上图代码和字节码对应关系可以看出,在同步代码块中获取锁时使用monitorenter指令,释放锁时使用monitorexit指令,且会有两个monitorexit,确保在当前线程异常时,锁正常释放,避免其他线程等待死锁。

所以synchronized的同步机制是依赖monitorenter和monitorexit指令实现的,而这两个指令操作的就是mLock对象的monitor锁,monitorenter尝试获取mLock的monitor锁,如果获取成功,则monitor中的计数器+1,同时记录相关线程信息,如果获取失败,则当前线程阻塞。

Monitor锁就是存储在MarkWord中的指向重量级锁的指针所指向的对象,每个对象在构造时都会创建一个Monitor锁,用于监视当前对象的锁状态以及持锁线程信息,

同步方法

testSynchronizedMethod其所对应的字节码如下图所示:

1-4-10-7

可以看到同步方法依赖在函数声明时添加ACC_SYNCHRONIZED标记实现,在函数被ACC_SYNCHRONIZED修饰时,调用该函数会申请对象的Monitor锁,申请成功则进入函数,申请失败则阻塞当前线程。

静态同步方法

testSynchronizedStaticMethod其所对应的字节码如下图所示:

1-4-10-8

和同步方法相同,同步静态方法也是在函数声明部分添加了ACC_SYNCHRONIZED标记,与同步方法不同的是,此时申请的是该类的类对象的Monitor锁。


扩展

上文中针对synchronized的java使用以及字节码做了说明,我们可以看出synchronized是依赖显式的monitorenter,monitorexit指令和ACC_SYNCHRONIZED实现,但是字节码并不是最靠近机器的一层,相对字节码,汇编又是怎么处理synchronized相关的字节码指令的呢?

我们可以通过获取java代码的汇编代码来查看,查看Java类的汇编代码需要依赖hsdis工具,该工具可以从https://chriswhocodes.com/hsdis/下载(科学上网),下载完成后,在Intellij Idea中配置Main类的编译参数如下图所示:

1-4-10-11

其中vm options详细参数如下:

-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=compileonly,*SynchronizedTestClass.testSynchronizedBlock -XX:CompileCommand=compileonly,*SynchronizedTestClass.testSynchronizedMethod -XX:+LogCompilation -XX:LogFile=/Volumes/Storage/hotspot.log

其中“compileOnly,”后面跟的是你要抓取的函数名称,格式为:*类名.函数名,LogFile=后指向的是存储汇编代码的文件。

环境变量配置如下:

LIBRARY_PATH=/Volumes/Storage/hsdis

这里的写法是:hsdis存储路径+/hsdis

随后再次运行Main.main即可看到相关汇编代码输出在运行窗口,通过分析运行窗口输出的内容,我们可以看到如下截图:

1-4-10-9

可以看出在运行时调用SynchronizedTestClass::testSynchronizedMethod时,进入synchronized需要执行lock cmpxchg以确保多线程安全,故synchronized的汇编实现为lock cmpxchg指令。

参考链接

https://juejin.cn/post/6844904038580879367

https://segmentfault.com/a/1190000014315651?u_atoken=1de4c0b1-3bbd-4f24-a1ab-a997765e1e1a&u_asession=01_GMzG_R8y1NzmMkw-_FjCBDyAkKZZeqX02Aj0fLUi3NY0u-oBsC7Dy3mYDlaYjVfX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K_YrukAhz0rn57pEYiZgVtjUe3R9QHfzEvknA4dzJmVTGBkFo3NEHBv0PZUm6pbxQU&u_asig=05zOyEpzkQtID72ACuBpeb8Glaue6lNWy0NQylEbR2Xc_7kNzOQ26VSExsqtnzBe0Xx0y_nHbPn6RxgW3P4ycjRX6ouZzhKXbyHd2wyK1BU-yIj5LEU571xoQb6N65-U8YiNzYYvkK1yxbcelpg93XN_0VgtmNHNEhLa9ouEeFbkf9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzbnmdT2ThIIvmF2G_c1IyFPFdBjB2ky9pbFgAVRR13T0H_8T8uYGNepqxdb-gLe1IO3h9VXwMyh6PgyDIVSG1W8yga-85GxBTGJYmsELhoUH8mRFOQ3P7irc0oGyG8T1Ftbng4zz7ikYTVlpim2ptLJ8hIoW81rm0R3x30M6VpJOmWspDxyAEEo4kbsryBKb9Q&u_aref=Vatq7Ew4O%2BKsO9AD17eeU57gwco%3D

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

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

相关文章

如何通过开源项目搭建私有云平台--第四步下:安装rancher 监控

第四步下:安装rancher 监控,缺告警 本来想监控与告警一起写,但最近几天研究了rancher的告警,按照文档说法,配置了但没有触发,网上找了一些资料,有的在rancer 2.6成功的,但我用同样的方法在2.7.…

09 【Sass语法介绍-函数指令】

1.前言 在之前的章节我们学习过 Sass 提供的各种各样的函数,那么如果我们需要自定定义函数来使用就需要用到函数指令 function了。本节内容我们来学习 Sass 函数指令的语法和使用,在 Sass 中自定义函数是必须要掌握的! 2.什么是 Sass 函数指…

又一款可视化神器,开源了!

在互联网数据大爆炸的这几年,各类数据处理、数据可视化的需求使得 GitHub 上诞生了一大批高质量的 BI 工具。 借助这些 BI 工具,我们能够大幅提升数据分析效率、生成更高质量的项目报告,让用户通过直观的数据看到结果,减低沟通成…

安卓项目如何做单元测试

前言 先说一下创建篇文章的目的,近期负责搭建公司的单元测试框架,于是查阅了网上的很多文章,以及参考了github上很多的项目例子,并且也进行了相当多的尝试。这其中花费了很多的精力,大约有两三周的时间,远…

淘系抓包流程(淘宝数据无法抓包解决方式)

淘系抓包流程 结合frida和adb工具以及mumu模拟器进行抓包。 具体的关系图: frida的安装 frida安装,直接安装官网的脚手架。frida官网使用python的pip安装,python > 3。 安装后使用查看版本命令来确认是否安装。 pip install frida-tools frida --ve…

【严重】VMware Aria Operations for Logs v8.10.2 存在反序列化漏洞(CVE-2023-20864)

漏洞描述 VMware Aria Operations for Logs前身是vRealize Log Insight,VMware用于处理和管理大规模的日志数据产品。 VMware Aria Operations for Logs 8.10.2版本中存在反序列化漏洞,具有 VMware Aria Operations for Logs 网络访问权限的未经身份验…

“SCSA-T学习导图+”系列:交换技术之STP

本期引言: 在通信工程当中,从物理层面上,我们可以采用冗余链路保证网络的健壮性。冗余是指出于系统安全和可靠性等方面的考虑,人为地对一些关键部件或功能进行重复的配置。当系统发生故障时,比如某一设备发生损坏&…

【Linux】Linux下的gbd调试,你学废了吗

操作系统核心数centos 3.10.032位单核 gbd调试方法-以线程运行时调试为例 线程死锁状态时查看栈升级gbd通过gdb在程序运行时进行调试 线程死锁状态时查看栈 在线程-线程安全之互斥中,我们自己写了一个模拟实现的线程死锁情况 我们用gbd调试查看了当前线程的调用&a…

拉链表制作

1.拉链表的应用场景 拉链表适合于:数据会发生变化,但是大部分是不变化的(即:缓慢变化维。还需要保留历史数据做分析的场景) 2.拉链表的形成过程 关键的过程 第四步:获取变化的数据(创建和修改…

Flask连接MySQL

本文章涉及到Flask框架和HTML内容,相关知识可查看链接 HTML-form表单和提交_html form 提交_小梁今天敲代码了吗的博客-CSDN博客https://blog.csdn.net/weixin_43780415/article/details/130110722 前端引入和html标签_小梁今天敲代码了吗的博客-CSDN博客https://b…

vscode下drawio无法使用

问题描述: Vscode下, 刚下载drawio这个插件,在vscode左边EXPLORER下,没有Test这个页面,导致vscode无法使用drawio。 解决办法: 在自己需要的目录下,新建一个文件,例如test,并命名为test.drawi…

数据结构算法

直接插入排序 1.从第一个元素开始,该元素可以认为已经被排序 2.取下一个元素tem,从已排序的元素序列从后往前扫描 3.如果该元素大于tem,则将该元素移到下一位 4.重复步骤3,直到找到已排序元素中小于等于tem的元素 5.tem插入到该元…

5.1劳动节,致敬最可爱的人!Cocos社区杰出贡献者出炉

Cocos 引擎的生态建设与繁荣,离不开社区开发者的辛勤付出。 2022.5 ~ 2023.5 年度期间,有这样一批 Cocos 社区开发者,他们使用 Cocos Creaor 引擎创作内容与产品、分享技术和经验,为 Cocos 社区默默贡献自己的一份力量&#xff0c…

改进YOLOv8:替换轻量化骨干网络Efficient V1、Efficient V2《重新思考卷积神经网络的模型缩放》)

这里写目录标题 1 EfficientNetV1中存在的问题2.EfficientNetV2网络框架3.YOLOv8添加Efficient V1代码yaml文件Efficient V1代码运行4. 添加Efficient V2代码yaml文件Efficient V2运行Efficient V1论文地址:https://arxiv.org/pdf/1905.11946.pdf Efficient V1代码地址:

Golang每日一练(leetDay0049) 二叉树专题(9)

目录 144. 二叉树的前序遍历 Binary-tree Preorder Traversal 🌟 145. 二叉树的前序遍历 Binary-tree Postorder Traversal 🌟 对比: 94. 二叉树的中序遍历 Binary-tree Inorder Traversal 🌟 146. LRU缓存 LRU Cache &am…

调用移动云OCR识别身份证

一.开通移动云OCR服务 在下面这个网址开通免费服务,,每个账号可免费使用500次,先要实名认证。 通用文字识别 (10086.cn)https://ecloud.10086.cn/home/product-introduction/Generalverify 有两种方式: 这里选择第二种 。 二…

炸裂的 Auto-GPT,帮我自动生成小视频!

大家好,我是程序员贺同学。 继前段时间爆火的 ChatGPT 后,又一个炸裂的开源项目 Auto-GPT 出现了。 仿佛一夜之间,AI 圈又出现了一个新晋顶流。我们来看看它有多🔥。 在 GitHub 上,仅最近不到两个礼拜,这个…

TCP/IP基础知识

文章目录 互联网与TCP/IP的关系TCP/IP与OSI参考模型硬件(物理层)互联网层(网络层)IPICMPARP 传输层TCPUDP 应用层WWW电子邮件(E-Mail)文件传输(FTP)远程登录(TELNET与SSH&#xff09…

Redis Set 用了 2 种数据结构来存储,到现在才知道

Sets 无序集合,他的功能就好像你熟悉的 Java 中的 HashSet 一样。集合是通过散列表实现的,所以添加、删除、查找元素的时间复杂度是 O(1)。 1. 是什么 Sets 是 String 类型的无序集合,集合中的元素是唯一的,集合中不会出现重复的数…

【百问百答】可靠性基础知识第三期

1.电连接器的基本性能有哪些? 三个基本性能:机械性能、电气性能和耐环境性能。 电连接器机械性能测试包括:插拔力测试、端子保持力测试、端子正向力测试、耐久性测试。 电气特性测试包括:绝缘电阻测试、 耐电压测试、 低电平电阻测试(LLCR…