【JUC进阶】01. Synchroized实现原理

news2024/11/25 3:52:35

目录

1、前言

2、Synchronized使用

2.1、对象锁(Instance Lock)

2.2、类锁(Class Lock)

2.3、方法锁(Method Lock)

3、原理分析

3.1、monitor对象

3.2、monitorenter

3.3、monitorexit

3.4、对象锁的原理

3.5、类锁的原理

3.6、方法锁的原理

4、Synchronized的优化与改进

5、小结


1、前言

Synchronized是在并发编程中很经常使用,Java中除了提供Lock等API来实现互斥以外,还提供了语法(关键字)层面的Synchronized来实现互斥同步原语,今天闲来聊聊Synchronized关键字。

2、Synchronized使用

Synchronized可以修饰代码块、方法或类。在多线程环境下,Synchronized能够确保同一时间只有一个线程访问被保护的代码块,从而避免了数据竞争和不一致的问题。

2.1、对象锁(Instance Lock)

对象锁是指对实例对象进行加锁,使得同一时间只有一个线程可以执行被保护的代码块。每个对象都有一个与之关联的监视器锁(Monitor Lock),也称为内部锁(Intrinsic Lock)或互斥锁(Mutex Lock)。只有获得了对象的锁,才能进入同步代码块执行。

示例代码:

public class MyClass {
    public  void synchronizedMethod() {
        synchronized (this) {
            // 同步代码块
            // ...   
        }

    }
}

synchronized关键字修饰的synchronizedMethod方法是一个同步方法,它的对象锁就是MyClass的实例对象。当多个线程调用这个方法时,只有一个线程能够获得对象锁,进入同步代码块执行,其他线程需要等待。

2.2、类锁(Class Lock)

类锁是指对类进行加锁,使得同一时间只有一个线程可以执行被保护的代码块。类锁是基于类对象而不是实例对象的,因此它在类的所有实例之间是共享的。

示例代码:

public class MyClass {
    public static synchronized void synchronizedMethod() {
        // 同步代码块
        // ...
    }
}

synchronized关键字修饰的synchronizedMethod方法是一个静态同步方法,它的类锁是MyClass类对象。因为类锁是在类级别上的,所以即使有多个MyClass的实例,它们共享同一个类锁。

2.3、方法锁(Method Lock)

方法锁是指对整个方法进行加锁,使得同一时间只有一个线程可以执行该方法。方法锁实际上是对象锁的一种特殊形式,它锁定的是该方法所属对象的实例。

示例代码:

public class MyClass {
    public synchronized void synchronizedMethod() {
        // 同步代码块
        // ...
    }
}

synchronized关键字修饰的synchronizedMethod方法是一个实例方法,它的方法锁就是该方法所属对象的实例。因此,同一时间只有一个线程能够执行该方法。

3、原理分析

我们先来写一段不带synchronized代码:

class SyncTest {
    public void method1() {
        method2();
    }

    private static void method2() {

    }
}

使用javac命令编译成.class文件,再通过反编译javap查看文件信息。

javap -verbose  xxx.class

可以看到如下信息:

加上synchronized代码后:

class SyncTest {
    public void method1() {
        synchronized (this) {

        }
        method2();
    }

    private static void method2() {

    }
}

再来javap反编译:

对比之下可以发现,加了synchronized方法多了monitorenter和monitorexit指令。

3.1、monitor对象

先来说说monitor对象。在Java中,每个对象都与一个Monitor对象关联。Monitor对象是用于实现Synchronized关键字的内部机制,它负责实现线程的互斥访问和等待/通知机制。每个Monitor对象都有一个锁标志和等待队列。而每个Java对象在内存中都有一个对象头,其中包含了Monitor相关的信息。

  1. 工作原理:
    • 当线程执行到进入Synchronized块或方法时,会尝试获取对象的Monitor锁。
    • 如果Monitor锁可用,线程获得锁并继续执行,同时锁标志设置为被当前线程持有。
    • 如果Monitor锁已经被其他线程持有,线程进入阻塞状态,并被放入等待队列。
    • 当持有锁的线程执行完Synchronized块或方法,并调用monitorexit指令释放锁时,等待队列中的一个线程被唤醒,然后竞争锁。
    • 线程获取到锁后,锁标志设置为被该线程持有,其他等待线程继续等待或进入就绪状态。
  2. 等待/通知机制:
    1. 在Monitor对象上等待的线程通过wait()方法进入等待状态,释放锁,并加入到等待队列中。
    2. 调用notify()方法可以唤醒等待队列中的一个线程,使其进入就绪状态。
    3. 调用notifyAll()方法可以唤醒等待队列中的所有线程,使它们进入就绪状态。
    4. 唤醒的线程会尝试重新获得锁,一旦获得锁,就可以继续执行。
  3. 锁升级:
    1. 偏向锁(Biased Locking):当一个线程获取到锁时,Monitor会记录该线程的Thread ID,以后该线程再次获取锁时就无需竞争。
    2. 轻量级锁(Lightweight Locking):当多个线程争用同一个锁时,锁会升级为轻量级锁,使用CAS(Compare and Swap)操作来加锁和解锁。
    3. 重量级锁(Heavyweight Locking):当轻量级锁获取锁的过程中发生竞争,锁会升级为重量级锁,使用互斥量来实现锁的获取和释放。

3.2、monitorenter

monitorenter指令用于获取对象的Monitor,即加锁操作。执行monitorenter指令时,首先会尝试获取对象的锁,如果锁可用,则线程获得锁,并继续执行后续的指令。如果锁已经被其他线程持有,则当前线程会进入阻塞状态,等待锁的释放。当线程成功获取到锁后,会将锁的计数器加1,表示当前线程持有锁的数量。

3.3、monitorexit

monitorexit指令用于释放对象的Monitor,即解锁操作。执行monitorexit指令时,会将锁的计数器减1。如果锁的计数器为0,表示当前线程已经释放了该锁,其他等待锁的线程可以继续竞争获取锁。如果锁的计数器仍大于0,表示当前线程仍持有锁,其他线程仍无法获取该锁。

网上借来一张图说明对象,对象监视器,同步队列以及执行线程状态之间的关系:

该图可以看出,任意线程对Object的访问,首先要获得Object的监视器,如果获取失败,该线程就进入同步状态,线程状态变为BLOCKED,当Object的监视器占有者释放后,在同步队列中得线程就会有机会重新获取该监视器。

3.4、对象锁的原理

当一个线程要执行被Synchronized修饰的同步代码块时,它需要先获得对象的监视器锁(Monitor Lock)。如果对象锁被其他线程占用,则该线程会进入阻塞状态,直到获取到对象锁后才能执行同步代码块。对象锁的实现是基于对象头中的标记位实现的,它通过CAS(Compare and Swap)操作来保证线程的互斥访问。

3.5、类锁的原理

类锁的实现与对象锁类似,不同之处在于类锁是基于类对象而不是实例对象的。类对象在JVM中只有一份,所以类锁在整个类的所有实例之间是共享的。类锁的实现同样是基于对象头中的标记位实现的。

3.6、方法锁的原理

方法锁实际上就是实例对象锁或类对象锁,其原理与对象锁和类锁相同。方法锁的加锁粒度是整个方法,即在进入方法前获取锁,在方法执行完毕后释放锁。

4、Synchronized的优化与改进

  1. 减小锁的粒度:在并发环境下,锁的争用是造成性能瓶颈的主要原因之一。通过减小锁的粒度,将一个大的同步代码块拆分成多个小的同步代码块,可以降低锁的竞争,提高并发性能。
  2. 使用局部变量代替实例变量:在某些情况下,可以将实例变量赋值给局部变量,然后在同步代码块内部使用局部变量,避免对实例变量的频繁访问,从而减少对锁的竞争。
  3. 使用读写锁:如果某个共享资源大部分时间是读取操作,少部分时间是写入操作,可以考虑使用读写锁(ReadWriteLock)。读写锁提供了更细粒度的锁控制,允许多个线程同时读取共享数据,而对写入操作进行互斥。
  4. 使用并发容器:JUC提供了一些并发容器,如ConcurrentHashMap、ConcurrentLinkedQueue等,它们在实现上使用了更复杂的锁机制,能够提供更好的并发性能。如果需要在多线程环境下使用容器,可以考虑使用这些并发容器。
  5. 使用Lock接口:除了Synchronized关键字外,JUC还提供了Lock接口及其实现类,如ReentrantLock、ReadWriteLock等。相比于Synchronized,Lock提供了更灵活的锁控制,可以手动获取和释放锁,并支持公平锁和非公平锁的选择。
  6. 避免死锁:死锁是多线程编程中常见的问题,它发生在多个线程相互等待对方释放锁的情况下。为避免死锁,应尽量减少锁的嵌套使用,按照相同的顺序获取锁,避免循环等待,以及合理设计锁的粒度。
  7. 考虑性能与安全的平衡:在并发编程中,性能和安全是需要平衡的。过多的同步操作可能会影响性能,而过少的同步操作可能会引发线程安全问题。需要根据具体情况综合考虑,选择适当的同步策略。

5、小结

通过合理使用Synchronized,我们可以有效地保护共享资源,避免数据竞争和不一致的问题。同时,我们也要注意锁的粒度、死锁的避免以及性能与安全的平衡,以提供高效可靠的多线程程序。

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

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

相关文章

库克和马斯克之后,比尔盖茨访华,美企认识到中国市场不可替代

微软创始人比尔盖茨已到达了中国,这是美国苹果CEO库克、特斯拉CEO马斯克之后访华的又一位美国重要企业家,那么美国企业家陆续访华是为了什么呢? 一、美企连遭挫折 苹果一季度的业绩显示营收、利润均下滑了个位数,但是苹果看到了隐…

AIGC 加持 Cocos,游戏开发需要几步?

近日,游戏行业知名的 B2B 大会 WN 2023 大会于土耳其首都伊斯坦布尔顺利举办。本次大会邀请了来自全球的游戏开发商、媒体、发行商、分发平台等行业决策者,共同探讨游戏行业未来发展态势,进一步拓展业务,并在世界范围内寻找新的合…

【力扣刷题 | 第十天】347.前k个高频元素 227 简单计算器

前言: 本篇将是最后一篇我们利用栈与队列来解决力扣问题,在下文我们将进入到数这一章,相对应的【夜深人静讲数据结构与算法】专栏中树也会及时更新。 347. 前 K 个高频元素 - 力扣(LeetCode) 给你一个整数数组 nums 和…

【JS】1714- 重学 JavaScript API - Geolocation API

❝ 前期回顾: 1. Page Visibility API 2. Broadcast Channel API 3. Beacon API 4. Resize Observer API 5. Clipboard API 6. Fetch API 7. Performance API 8. WebStorage API 9. WebSockets API 10. Fullscreen API ❞ 本文将深入探讨 Geolocation API 的概念、使…

华为OD机试真题 JavaScript 实现【关联子串】【2023Q1 100分】,附详细解题思路

一、题目描述 给定两个字符串str1和str2,str1进行排列组合只要有一个为str2的子串则认为str1是str2的关联子串,请返回子串在str2的起始位置,若不是关联子串则返回-1。 二、输入描述 qwe dsgfasgfwe 三、输出描述 -1 四、解题思路 读取…

009、体系架构之HTAP

HTAP HTAP技术传统的HTAP解决方案HATP的要求TiDB的HTAP架构TiDB的HTAP特性使用场景 MPP HTAP技术 传统的HTAP解决方案 HATP的要求 可扩展性 分布式事务分布式存储 同时支持OLTP与OLAP 同时支持行存和列存OLTP与OLAP业务隔离 实时性 行存与列存数据实时同步 TiDB的HTAP架构 …

Committer 迎新!这次是来自阿里云的同学

点击蓝字 关注我们 迎新! 截至今天,Apache DolphinScheduler 项目在 GitHub 上的 Star 数已突破 10.6K,贡献者人数也突破了 470 人。社区的不断壮大,离不开每位 Contributor 的支持。 最近,Apache DolphinScheduler 又…

AI模型部署实战:利用CV-CUDA加速视觉模型部署流程

本文首发于公众号【DeepDriving】,欢迎关注。 CV-CUDA简介 随着深度学习技术在计算机视觉领域的发展,越来越多的AI算法模型被用于目标检测、图像分割、图像生成等任务中,如何高效地在云端或者边缘设备上部署这些模型是工程师迫切需要解决的问…

Android 13(T) - 智能指针

Android有一套自己的智能指针管理办法,并且将其运用在源码的各个角落,所以学习Media框架之前,我们有必要先了解下Android智能指针。 本节代码源自于Android 13(T),参考 (aospxref.com) 1 概述 与智能指针相关的总共有5个类&#…

某小厂面试加答案(6.15)

看 Java 面试题就去 www.javacn.site 磊哥新推出《企业面经和答案》栏目,最近会持续更新,欢迎大家订阅此账号查看,或访问 www.javacn.site 查看。 面经来源于牛客,如下图所示: https://www.nowcoder.com/feed/main/det…

OpenAI的创始人World Coin项目介绍

🎯 在一个崇高的目标支持下,不停地工作,即使慢,也一定会获得成功。—— 爱因斯坦 如果你对项目感兴趣请联系v:weixin605405145 一、项目速览 项目背景 Worldcoin由OpenAI的创始人Sam Altman于2019年创立,就…

【C++】的继承

继承的概念及定义 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构…

010、体系架构之TiFlash

TiFlash TiFlash 功能架构异步复制一致性读取场景选择是选择TiKV还是TiFLash TiFlash 功能 异步复制一致性读取(写虽然是异步,但读可以做到一致性)引擎智能选择计算加速 架构 TiFLASH 也是通过raft 算法进行同步,但它不怎么消耗资源,因为它…

ProGuard 进阶系列(二)配置解析

书接上文,从开源库中把代码下载到本地后,就可以在 IDE 中进行运行了。从 main 方法入手,可以看到 ProGuard 执行的第一步就是去解析参数。本文的内容主要分析源码中我们配置的规则解析的实现。 在上一篇文章末尾,在 IDE 中&#x…

Vue Router4

后端路由 客户端请求不同的URL服务器匹配URL并给一个Controller处理Controller处理完返回渲染好的HTML页面或数据给前端 优点: 不需要单独加载js和css,直角交给浏览器展示,有利于SEO优化 缺点: 页面有后端人员编写或由前端人员…

告别里程焦虑:深蓝S7超级增程打造超长续航

提起新能源汽车,估计许多人第一时间都会想要查看它的续航里程。 虽然如今的新能源汽车在续航里程上较过去已经有了很大改进,但是稀缺的充电桩和漫长的充电时间,仍然无法让需要长途出行的用户摆脱里程焦虑。 那么问题就来了:有没有…

基于协同过滤算法的外贸出口电子电器产品的推荐系统的设计与实现源码+文档

博主介绍:✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 项目名称 基于协同过滤算法的外贸出口电子电器产品的推荐系统的设计与实现源码文档 视频演示 https://www.bilibili.com/video/BV1HW4y197Fe/ 系统介绍 摘 要 …

dubbo源码之-ExtensionInjector

dubbo源码之-ExtensionInjector 概述源码入口Extension 是如何获取到?SpiExtensionInjector 概述 其实ExtensionInjector 非常简单, 我们知道dubbo有ioc注入的功能, 是靠的set方法注入,对应的底层源码主要是ExtensionInjector 如…

MySQL数据库语言一、DDL

😘作者简介:正在努力的99年打工人。 👊宣言:人生就是B(birth)和D(death)之间的C(choise),做好每一个选择。 🙏创作不易,动…

华为OD机试真题B卷 JavaScript 实现【分班】,附详细解题思路

一、题目描述 幼儿园两个班的小朋友在排队时混在了一起,每位小朋友都知道自己是否与前面一位小朋友是否同班,请你帮忙把同班的小朋友找出来。 小朋友的编号为整数,与前一位小朋友同班用Y表示,不同班用N表示。 二、输入描述 输…