一把锁的两种承诺:synchronized如何同时保证互斥与内存可见性?

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情

在多线程环境中,临界区(Critical Section)是指一次只能由一个线程执行的代码段,这些代码通常涉及对共享资源(如变量、数据结构、文件或数据库连接)的访问或修改。临界区的存在是为了解决并发控制中的两大核心问题。 1)数据不一致性:如果多个线程同时对共享资源进行写操作,可能会破坏数据的完整性,导致其状态与预期不符。 2)竞态条件:程序的执行结果依赖于线程调度和执行的偶然顺序,这使得程序行为变得不可预测,难以调试。

为了保护临界区,Java提供了多种互斥(Mutual Exclusion)机制,其中synchronized关键字是最常用且强大的工具之一。 synchronized实现互斥的基础是Java中的每一个对象都可以作为锁,这个锁是排他的,在任意时刻只有两种状态:被占用和未被占用。当线程请求一个由其他线程持有的锁时,请求的线程会被阻塞,直到锁被释放。这种机制确保了在任何时刻,只有一个线程能够进入临界区执行代码。 synchronized 有两种使用方式。 1)synchronized修饰方法:锁是当前实例对象。它修饰的方法称为同步方法。

public synchronized void method() {
    // ...
}

2)synchronized修饰代码块:锁是synchronized括号里配置的对象。它修饰的代码块称为同步代码块。

public void method() {

    synchronized (this) {
        // ...
    }

}

synchronized与happens-before关系 在Java内存模型中,对synchronized关键字建立如下的happens-before关系:释放锁的操作happens-before之后对同一把锁的获取的锁操作。

class LockingExample {
    int x = 0;
    public synchronized void set() {    // 1
        x++;                            // 2
    }                                   // 3

    public synchronized void get() {    // 4
        int i = x;                      // 5
        // ......
    }                                    //6
}

假设线程A执行set()方法,随后线程B执行get()方法。 假设线程A获取锁执行set()方法,在set()方法中,对共享变量x自增+1,然后释放锁。线程B获取锁执行get()方法,在get()方法中,读取变量x,并赋值给本地变量i,然后释放锁。根据happens-before规则,可以确定线程A对x的修改happens-before线程B对x的读取,从而保证了数据的一致性。 这个过程建立的happens-before关系可以分为3类。 1)程序次序规则:1 happens-before 2,2 happens-before 3;4 happens-before 5,5 happens-before 6; 2)监视器锁规则:3 happens-before 4; 3)happens-before的传递性规则: happens-before 5。 上述happens-before关系的图形化表现形式如下。

synchronized内存语义
synchronized释放锁的内存语义:当线程释放锁时,Java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中。 A线程释放锁后,共享数据的状态如图所示。

synchronized获取锁的内存语义:当线程获取锁时,Java内存模型会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。 B线程释放锁后,共享数据的状态如图所示。

对比锁释放-获取与volatile写-读的内存语义可以看出:锁释放与volatile写有相同的内存语义;锁获取与volatile读有相同的内存语 义。这表明synchronized不仅提供了互斥访问的同步机制,还具备了volatile的内存可见性保障。

13.synchronized总结:怎么保证可见性、有序性、原子性? synchronized是怎么保证原子性、可见性、有序性的? 阅读详情

相关推荐

EndNote全文检索实战:如何快速配置学校图书馆资源并精准查找文献

本文详细介绍了EndNote全文检索功能的实战配置方法,核心在于通过正确设置OpenURL链接解析器,无缝对接学校图书馆资源,实现一键精准获取文献全文。文章提供了从理解机制、四步配置法到进阶技巧的完整指南,帮助科研人员高效管理海量PDF,构建个人知识体系,大幅提升文献调研写作效率。

weixin_29228781的博客 238

synchronized如何保证原子性、可见性、有序性

原理 原子性:加锁和释放锁; 可见性: 加了Load屏障和Store屏障,释放锁flush数据,加锁会refresh数据; 有序性:Acquire屏障和Release屏障,保证代码块内部可以重排,但是代码块内部和代码块外部的指令是不能重排的。 如何保证原子性: java对象是分为对象头和实例变量两块,其中实例变量就是对象那些变量数据,然后对象头包含了两块内容,一是 Mark Word(含hashCode、锁数据、GC数据等),另一个是Class Metadata Address(包含了指向类的元数据指针

qq_33590654的博客 1697

2026最新华为OD机试新系统机考真题- 真题题库目录|全覆盖题库 + 逐点算法考点详解

2026年4月1日起,华为OD机试正式切换至新系统模式!采用力扣模式,无需自己写输入! 难度大幅度降低。正在备考华为OD机试的同学,优先刷新系统机试真题,可大幅提升通过率。 新系统考题考点旧版一致,无需重新适应。本专栏所有算法题均配有详细思路解析,并提供C语言、JS、C++、Python、Go、Java语言源码实现。订阅后私信即可开通专属OJ刷题权限,高效备考一次上岸!

算法大师! 959

synchronized 如何保证可见性的?

虽然synchronized关键字提供了对共享资源的访问控制,但是它会造成一些性能损耗,因为每次使用synchronized时,都会涉及到锁的获取和释放。原子性操作: synchronized 保证了被同步的代码块在同一时刻只能被一个线程执行,从而确保了代码块中的操作是原子的,不会被中断。而当这个线程离开synchronized代码块时,会强制将本地内存中的修改刷新回主内存,这样其他线程就可以看到最新的值。当线程获取锁时,它会清除在本地内存中的变量值,确保了这些变量在锁释放前的修改对其他线程可见。

m0_54187478的博客 1043

synchronized锁怎么保证可见性

synchronized锁怎么保证可见性

java架构师分享,有问题可以联系我 310

关于synchronized可见性

在没有发生锁消除的前提下,当当前线程遇到synchronized关键字时会把工作内存中的缓存清空,这样当前线程栈中共享变量的“副本”就没有了,当再次要用到共享变量时,只能去主内存中拷贝一份新的“副本”,这样就保证了每次读取的共享变量的值都是最新的。

buglzm的博客 781

Java内存模型深度解析可见性、有序性原子性的博弈

Java内存模型通过定义可见性、有序性和原子性的规则,为并发编程构建了一个可预测的基础。这三者之间的博弈是并发程序设计的核心。过度保守地使用锁会牺牲性能,而过度追求性能又可能破坏线程安全。深入理解JMM,意味着能够洞察并发代码背后的内存交互细节,从而能够精准地选择同步机制,在程序的正确性和运行效率之间找到最佳平衡点。掌握这场博弈,是每一位高级Java开发者的必备技能。

xiongchaosuper的专栏 637

Java并发编程:如何正确使用 volatile、synchronized 和 final 关键字

本文介绍了 Java 中的 volatile、synchronized 和 final 三个关键字的作用、原理以及使用场景。volatile 关键字可以保证可见性和有序性,synchronized 关键字可以保证可见性和有序性,同时还可以保证原子性,final 关键字可以保证变量的不可变。在实际开发中,需要根据具体的需求选择合适的关键字来保证程序的正确性和性能。

乐只悠悠 1248

第三章:《并发控制的温柔陷阱》

并发,像是魔术师的舞台,让多个任务在同一时间段内看似同时进行,虽然实际执行可能是交错的,但它巧妙地利用了CPU的切换时间,使得每个任务都能向前推进,提高了整体的响应速度。而并行,则是真正意义上的“同时”,如同多条赛道上的赛跑,多个任务能在不同的处理器核心上同时执行,这对于高度计算密集型的应用来说,无疑是性能提升的利器。则是厨房门上的一把锁。由于涉及到线程的阻塞和唤醒,开销较大,但在JDK 1.6之后,通过偏向锁、轻量级锁和重量级锁的优化,性能有了显著提升,尤其在竞争不激烈的情况下。

fkfgq2008的博客 836

禁止指令重排序 Happens-Before 原则

本文系统分析了指令重排序Happens-Before原则,重点比较了volatilesynchronized的特性差异。指令重排序包括编译器、处理器和内存重排序三种类型,Happens-Before原则通过8条核心规则保证多线程可见性和有序性。volatile和synchronized在原子性、可见性、有序性、锁机制、性能等方面存在显著差异:volatile适用于简单变量操作,保证可见性和有序性;synchronized适用于复杂临界区操作,保证原子性。面试需重点掌握Happens-Before原则、

weixin_43076660的博客 883

Java内存模型解析:如何写出线程安全的代码

你看不见它,但它正在你的多核处理器里悄无声息地制造着 bug。Java 内存模型(JMM)不是一个存在于虚拟机规范里的抽象概念,而是决定你的并发代码是正常运行还是随机崩溃的底层契约。很多开发者以为加了或者用了volatile就万事大吉,却不知道这些关键字背后到底解决了什么问题。今天,我们就把 JMM 的皮剥开,看看那些被编译器和 CPU 偷偷摸摸重排的指令,以及它们如何杀死你的线程安全。

2501_90410679的博客 7

深入理解Java内存模型:从JVM到并发编程实践

本文深入解析Java内存模型(JMM)的核心概念及其在并发编程中的应用。首先指出JMM的诞生背景是为了解决硬件差异带来的内存一致性问题,提供统一的内存访问语义。重点剖析了JMM的主内存工作内存交互模型,以及happens-before原则如何保证线程间的操作可见性和顺序性。实践部分详细介绍了volatile关键字和锁机制的正确使用场景:volatile适用于保证可见性的原子操作,而synchronized和Lock则能确保复合操作的原子性。通过理解JMM的内存交互规则和同步机制,开发者能够编写出更健壮、高

Clh3096606627的博客 330

Java内存模型对并发编程的实践影响

让程序正确,让程序高效。如果彻底禁止重排序,所有线程安全,但性能像单线程;如果完全允许重排序,性能拉满但无法编程。定义一组最小约束,让遵守约束的程序获得正确性,同时给编译器和 CPU 留出充分的优化空间。你写并发代码,就是在这条边界上走钢丝。多学一条 JMM 规则,你就少踩一个坑;少学一条,你迟早会踩。别再问“为什么我加了 volatile 还是不对”这样的问题,先检查你的复合操作、检查你的对象逸出、检查你的锁粒度。JMM 不会对你仁慈,但它会对那些理解并尊重它的人,给予稳定的正确性和可观的性能回报。

2501_90410679的博客 7
上一篇: 十年大厂员工终明白:MySQL性能优化的尽头,是对B+树的极致理解
下一篇: Spring Security 该怎么玩 ?
程序员职业指南
博客等级 码龄4年 2107粉丝 960原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值