synchronized同步锁 : 原理到锁升级及历史演进的解析

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: synchronized同步锁 : 原理到锁升级及历史演进的解析

synchronized何如保证多线程的运行安全

synchronized关键字是一个用于同步访问共享资源的机制,它可以确保并发编程中的三个关键要素:原子性、可见性和有序性。下面将分别解释这三个要素以及synchronized是如何保证它们的。

1. 原子性(Atomicity)

原子性是指一个操作或者多个操作要么全部执行完成,要么全部不执行,不会出现部分执行的情况。在Java中,synchronized通过在同一时间只允许一个线程访问被锁定的代码块或方法来保证原子性。当一个线程进入synchronized代码块或方法时,其他试图访问该资源的线程将被阻塞,直到第一个线程释放锁。

2. 可见性(Visibility)

可见性是指一个线程对共享变量的修改对其他线程是可见的。在Java中,synchronized通过内存屏障(Memory Barrier)来保证可见性。当一个线程释放synchronized锁时,它会将本地缓存中的变量值刷新到主内存中,当其他线程获取synchronized锁时,它会从主内存中读取最新的变量值,从而保证了可见性。

3. 有序性(Ordering)

有序性是指程序执行的顺序按照代码的先后顺序执行。在并发编程中,由于编译器优化和处理器乱序执行等因素,程序的实际执行顺序可能与代码的编写顺序不一致。synchronized通过禁止指令重排来保证有序性。当一个线程进入synchronized代码块或方法时,它会看到一个一致的程序执行顺序,即按照代码编写的顺序执行。


需要注意的是,虽然synchronized可以保证原子性、可见性和有序性,但它并不是解决所有并发问题的银弹。过度使用synchronized可能导致性能问题,如锁竞争和死锁。因此,在使用synchronized时,需要仔细考虑并发访问的模式和锁的范围,以确保在保证正确性的同时获得良好的性能。

一、synchronized原理

synchronized是Java中的内置锁,通过对象头中的锁状态标志位和锁记录(Lock Record)来实现。每个Java对象都有一个对象头,其中包含了锁状态标志位、指向锁记录的指针等信息。

Java 对象在内存中布局分为三部分:对象头、实例数据和对齐填充。对象头中包含了关于锁的信息。具体来说,对象头中有一个 Mark Word,用于存储对象的哈希码、分代年龄、锁状态等信息。

当一个线程尝试获取某个对象的synchronized锁时,如果该对象的锁状态为无锁状态,则JVM会在当前线程的栈帧中创建一个锁记录(Lock Record),并将其与对象的对象头关联起来。此时,该线程就成功获取了对象的锁,可以执行同步代码块。


如果其他线程也尝试获取该对象的锁,则会被阻塞,直到持有锁的线程释放锁。当持有锁的线程执行完同步代码块后,会将锁状态设置为无锁状态,并唤醒等待队列中的一个线程,使其获取锁并执行同步代码块。

二、锁升级过程

在Java 6之前,synchronized的实现是基于重量级锁的,即当线程尝试获取锁失败时,会被阻塞并导致用户态和内核态的切换,性能开销较大。从Java 6开始,JVM对synchronized的实现进行了优化,引入了锁升级的过程,包括无锁状态、偏向锁、轻量级锁和重量级锁四种状态。


java中的锁升级是一个为了提高并发性能而设计的过程。在Java 6及之后的版本中,synchronized关键字的实现不再是单一的重量级锁,而是引入了偏向锁、轻量级锁和重量级锁等多种锁状态,这些状态可以根据竞争情况动态转换。

无锁状态:

对象刚开始时处于无锁状态,也就是没有任何线程持有该对象的锁。

偏向锁:

为了减少无竞争情况下的锁开销,JVM引入了偏向锁。当一个线程首次访问同步代码块时,它会在对象头和当前线程的栈帧中记录偏向的线程ID。这样,在后续的执行中,如果仍然是同一个线程访问该同步代码块,JVM就可以判断出来,并允许该线程无锁地执行同步代码。偏向锁实际上是一种延迟加锁的机制,它的目标是消除无竞争情况下的同步原语,进一步提高程序的运行性能。


但是,当有其他线程尝试获取这个偏向锁时,偏向锁就会撤销,并尝试升级为轻量级锁。


轻量级锁:

轻量级锁是为了减少线程阻塞而设计的。当偏向锁撤销后,或者多个线程交替执行同步代码块时,锁会升级为轻量级锁。轻量级锁的加锁过程是通过CAS操作实现的,它试图将对象头的Mark Word替换为指向线程栈帧中锁记录的指针。如果成功,则当前线程获得锁;如果失败,说明存在竞争,此时会尝试自旋等待,即让当前线程空转一段时间,然后再次尝试获取锁。


如果自旋等待达到一定的次数仍然没有获取到锁,那么轻量级锁就会升级为重量级锁。

重量级锁

重量级锁是Java中最基础的锁机制,它的实现依赖于操作系统的互斥量(Mutex)。当轻量级锁无法满足性能需求时,会升级为重量级锁。此时,未获取到锁的线程会被阻塞,并进入等待状态,直到持有锁的线程释放锁。由于重量级锁涉及到用户态和内核态的切换,因此它的性能开销相对较大。


重量级锁的实现依赖于底层的 Monitor 机制。每个对象都有一个与之关联的 Monitor,当线程尝试获取重量级锁时,会被放入 Monitor 的入口等待队列中。如果获取锁失败,线程会被阻塞并放入等待队列,直到持有锁的线程释放锁。


锁升级的过程是动态的,JVM会根据当前的竞争情况选择合适的锁策略。在无竞争或低竞争的情况下,偏向锁和轻量级锁能够显著提高程序的并发性能;而在高竞争的情况下,重量级锁则提供了可靠的线程同步机制。这种设计使得Java的synchronized关键字在不同的场景下都能表现出良好的性能。

三、优缺点

优点:

简单易用:synchronized是Java语言内置的同步机制,使用起来非常简单。

自动释放锁:当同步代码块执行完成后,JVM会自动释放锁,无需手动操作。

可重入锁:一个线程可以多次获取同一个锁,而不会导致死锁。

缺点:

无法中断等待:当一个线程在等待获取锁时,无法被中断,只能等待持有锁的线程释放锁。

无法设置等待超时:无法为等待获取锁的操作设置超时时间。

无法实现公平锁:synchronized锁是非公平的,可能会导致某些线程长时间无法获取锁。

四、synchronized升级优化的历史演进过程

版本的升级,synchronized也经历了一系列的优化和改进,以适应现代多核处理器架构和提高并发性能。以下是synchronized在同步版本的升级优化过程中的主要历史演进:

重量级锁:

在JDK 1.0和1.2版本中,synchronized的实现是基于重量级锁的。重量级锁依赖于底层操作系统的互斥量(Mutex)来实现线程同步。当一个线程尝试获取锁时,如果锁已经被其他线程持有,则该线程会被阻塞,并进入等待状态。这种实现方式在锁竞争激烈的场景下会导致线程频繁挂起和唤醒,从而带来较大的性能开销。


引入偏向锁和轻量级锁:

为了优化synchronized的性能,JDK 1.6引入了偏向锁和轻量级锁。偏向锁是一种优化策略,它假设在多数情况下,锁被同一个线程持有的时间相对较长,并且锁竞争不激烈。当一个线程首次访问同步代码块时,会在对象头和当前线程的栈帧中记录偏向的线程ID,这样后续该线程访问时无需再进行锁操作。如果发生锁竞争,偏向锁会升级为轻量级锁。


轻量级锁通过自旋等待(CAS操作)来尝试获取锁,避免线程立即挂起。如果自旋等待达到一定次数仍无法获取锁,则会升级为重量级锁。这种优化策略在锁竞争不激烈的场景下可以显著提高性能。


锁消除和锁粗化:

除了偏向锁和轻量级锁之外,JDK还引入了其他优化策略来进一步提高synchronized的性能。锁消除是一种编译器优化技术,通过逃逸分析来判断对象是否只会被一个线程访问。如果是这样的话,就可以消除对该对象的锁操作,从而避免不必要的同步开销。


锁粗化是与锁消除相反的优化策略。当一系列连续的锁操作都在同一个同步块中进行时,可以将这些锁操作合并成一个更大的同步块,以减少锁的获取和释放次数,从而提高性能。


适应性自旋和锁升级:

在后续的Java版本中,对synchronized的优化还包括适应性自旋和锁升级。适应性自旋是一种动态调整自旋等待时间的策略,根据之前的自旋成功率和锁持有者的状态来动态决定自旋的次数。这样可以在一定程度上减少无用的自旋等待,提高性能。


需要注意的是,虽然synchronized在Java版本中经历了一系列的优化和改进,但它仍然是一个重量级的同步机制。在高性能要求的场景下,可能需要考虑使用其他更轻量级的同步原语,如ReentrantLock、StampedLock等。这些同步原语提供了更细粒度的锁控制,能够更好地适应不同的并发场景。

再来看几个问题:

为什么JDK18之后,偏向锁更被标记为废弃?

在 JDK 18 中,偏向锁被标记为废弃的原因主要是基于实际的使用情况和性能分析。偏向锁的设计初衷是在无竞争或低竞争的情况下提高性能,通过减少不必要的锁操作来降低开销。然而,在实际应用中,JVM 开发者发现偏向锁并不总是能够提供预期的性能提升,有时甚至会成为性能瓶颈。

以下是一些导致偏向锁被废弃的关键因素:

复杂性:偏向锁的实现相对复杂,需要维护额外的锁状态和线程信息。这种复杂性不仅增加了开发和维护的成本,还可能引入潜在的错误和性能问题。


适用性有限:偏向锁主要适用于长时间持有锁且竞争不激烈的场景。然而,在实际应用中,很多锁的使用模式并不符合这个假设。如果锁被频繁地获取和释放,或者存在高度的竞争,偏向锁的优势就会大打折扣。


性能开销:尽管偏向锁的设计初衷是为了提高性能,但在某些情况下,它可能会导致额外的性能开销。例如,当偏向锁被撤销并升级为轻量级锁或重量级锁时,需要进行额外的锁状态转换和线程调度操作,这些操作可能会消耗大量的CPU资源。


其他同步原语的改进:随着Java并发包的不断演进,出现了更多更高效的同步原语,如 ReentrantLock、StampedLock 等。这些同步原语提供了更细粒度的锁控制,能够更好地适应不同的并发场景,因此在某些情况下可能更优于偏向锁。


基于以上考虑,JVM 开发者决定在 JDK 18中废弃偏向锁,以简化锁的实现和降低维护成本。同时,他们鼓励开发者根据具体的应用场景选择更合适的同步原语来实现并发控制。需要注意的是,即使偏向锁被废弃,它在早期版本的JDK中仍然可用,但建议在新版本中使用其他推荐的同步机制。

synchronized 的实现涉及哪些底层操作系统的支持?

synchronized 的实现涉及到了 Java 虚拟机(JVM)层面的锁机制,以及底层操作系统对线程和进程同步的支持,从操作系统角度来看涉及以下内容:

线程调度

当线程因为竞争锁而被阻塞时,操作系统会负责线程的调度。操作系统会根据不同的调度算法(如时间片轮转、优先级调度等)来决定哪个线程应该获得 CPU 时间片并执行。

互斥量(Mutex):

重量级锁的实现通常依赖于操作系统的互斥量机制。互斥量是一种同步原语,用于保护临界区资源,确保同一时间只有一个线程可以访问临界区。

信号量(Semaphore):

有时,synchronized 的实现还可能涉及到操作系统的信号量机制。信号量是一种用于控制多个线程对共享资源访问的同步原语,它可以维护一个计数器,表示可用资源的数量。

自旋锁(Spinlock):

轻量级锁在自旋等待时,可能会使用到自旋锁。自旋锁是一种特殊的锁,当线程无法获取锁时,它会持续检查锁的状态,而不是立即被阻塞。这种策略在锁被持有时间较短,且线程切换开销较大的场景下可能更有效。

内存屏障(Memory Barrier):

为了确保线程安全,JVM 在实现 synchronized 时还需要考虑内存模型。Java 内存模型(JMM)定义了一组规则,用于确保线程间的正确同步。为了实现这些规则,JVM 可能会插入内存屏障指令,以确保指令的顺序性和可见性。


总结来说,synchronized 的实现涉及到了 Java虚拟机层面的多种锁状态和对象头信息,以及底层操作系统对线程调度和同步原语的支持。这些机制共同作用,确保了多线程环境下对共享资源的安全访问。

相关文章
|
2月前
|
存储 缓存 算法
HashMap深度解析:从原理到实战
HashMap,作为Java集合框架中的一个核心组件,以其高效的键值对存储和检索机制,在软件开发中扮演着举足轻重的角色。作为一名资深的AI工程师,深入理解HashMap的原理、历史、业务场景以及实战应用,对于提升数据处理和算法实现的效率至关重要。本文将通过手绘结构图、流程图,结合Java代码示例,全方位解析HashMap,帮助读者从理论到实践全面掌握这一关键技术。
113 14
|
3月前
|
运维 持续交付 云计算
深入解析云计算中的微服务架构:原理、优势与实践
深入解析云计算中的微服务架构:原理、优势与实践
134 3
|
6天前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
52 22
解析静态代理IP改善游戏体验的原理
|
3天前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
46 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
1月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
2月前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
120 16
|
2月前
|
网络协议 安全 网络安全
探索网络模型与协议:从OSI到HTTPs的原理解析
OSI七层网络模型和TCP/IP四层模型是理解和设计计算机网络的框架。OSI模型包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,而TCP/IP模型则简化为链路层、网络层、传输层和 HTTPS协议基于HTTP并通过TLS/SSL加密数据,确保安全传输。其连接过程涉及TCP三次握手、SSL证书验证、对称密钥交换等步骤,以保障通信的安全性和完整性。数字信封技术使用非对称加密和数字证书确保数据的机密性和身份认证。 浏览器通过Https访问网站的过程包括输入网址、DNS解析、建立TCP连接、发送HTTPS请求、接收响应、验证证书和解析网页内容等步骤,确保用户与服务器之间的安全通信。
172 3
|
3月前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
117 17
|
3月前
|
存储 供应链 算法
深入解析区块链技术的核心原理与应用前景
深入解析区块链技术的核心原理与应用前景
100 0
|
3月前
|
机器学习/深度学习 存储 人工智能
政务部门人工智能OCR智能化升级:3大技术架构与4项核心功能解析
本项目针对政务服务数字化需求,建设智能文档处理平台,利用OCR、信息抽取和深度学习技术,实现文件自动解析、分类、比对与审核,提升效率与准确性。平台强调本地部署,确保数据安全,解决低质量扫描件、复杂表格等痛点,降低人工成本与错误率,助力智慧政务发展。

热门文章

最新文章

推荐镜像

更多