文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java程序员进阶必备:深入分析 Synchronized 原理

2024-12-02 05:26

关注

深入分析 Synchronized 原理

我们在开发中肯定会遇到在同一个 JVM 中,存在多个线程同时操作同一个资源时,此时需要想要确保操作的结果满足预期,就需要使用同步方法。

官方解释:同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。

官方推荐使用的同步方法 (JDK 1.6后):Synchronized 基于 JVM 实现(此次主角);当然还有 ReentrantLock 基于 JDK 实现的。

我们先简单地热个身,举一个常用 synchronized 的方式(锁的是该类的实例对象)。

public class SynchronizedCodeTest {
public void testSynchronized() throws InterruptedException {
synchronized (this) {
System.out.println("进入同步代码块");
Thread.sleep(100);
System.out.println("离开同步代码块");
}
}
public static void main(String[] args) throws InterruptedException {
new SynchronizedCodeTest().testSynchronized();
}
}

Synchronized 常用场景

任何对象(都有Mark Word结构,后面会详细描述) 都可以能作为 synchronized 锁的对象,根据使用的方式不同,锁的对象和对应的粒度也是有所不同。

并发编程三大特性

简单回顾了下 synchronized ,一聊到锁就会提到 原子性、有序性、可见性,简单的介绍下这些(就不具体展开说明了,有需要的读者可以查阅相关资料,或者感兴趣的话我后续补充)。

原子性

原子性:一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

简单理解为:如果将下单和支付2个操作看作一个整体,只要其中一个操作失败了,都算失败,反之成功。

有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。

大家可能或多或少,有听说过 Java 为了提高性能允许重排序(编译器重排序 和 处理器重排序),因此程序执行可能出现乱序也是由此而来。

简单理解为:有序性保证了 同样的代码 在多线程和单线程执行的最后结果相同,按照代码的先后顺序执行。

可见性

可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

某个类的一个成员变量 Integer A = 0;

# 线程1执行操作
A = 10
# 与此同时 线程2执行操作(B的值是0,而不是10,这就是可见性的问题)
Integer B = A;

# 常用的解决方案使用:volatile修饰 A 或者 使用synchronized修饰代码块 都可以解决这个问题

既然提到 synchronized 再多延伸出2个特性。

可重入性

synchronized monitor(锁对象) 有个计数器,获取锁时 会记录当前线程获取锁的次数,在执行完对应的代码块之后,计数器就会-1,直到计数器清零,锁就会被释放了。

不可中断性

不可中断:一个线程获取锁之后,另外一个线程处于阻塞或者等待状态,前一个不释放,后一个也一直会阻塞或者等待,不可以被中断。

Synchronized是不可中断,而 ReentrantLock是可中断(二者比较重要的区别之一)。

Synchronized 字节码

介绍完一些基本的特性后,我们正式开始进入 synchronized 实现原理分析。

# 将上面 热身例子反编译成字节码
javac -verbose SynchronizedCodeTest.java
javap -c SynchronizedCodeTest

我们主要关注下,monitorenter 和 monitorexit 这2个指令,对应的是 当前线程获取锁&计数器加一 和 释放锁&计数器减一。多个线程获取对象的监视器monitor获取是互斥。

对象,对象监视器,同步队列以及执行线程状态之间的关系

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

Java 对象头(Mark Word)

前面提到所有对象都可以作为synchronized锁的对象,在同步的时候是获取对象的monitor,即操作Java对象头里的Mark Word 。

下面是32位为JVM Mark Word默认存储结构(无锁状态)

锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。

接下来分别介绍这三种锁的实现原理和步骤与上图结合思考。

偏向锁

HotSpot的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

偏向锁的获取

当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁):如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。

偏向锁的撤销

偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。

偏向锁的获得和撤销流程

线程1--展示了偏向锁获取的过程。

线程2--展示了偏向锁撤销的过程。

轻量级锁

轻量级锁介于 偏向锁与重量级锁之间,竞争的线程不会阻塞。

轻量级加锁

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为 Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

轻量级解锁

轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。

轻量级锁及膨胀流程图

因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。

当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

重量级锁

Synchronized 是通过对象内部的一个叫做 监视器锁(Monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为 “重量级锁”。

三种锁的比较

分析了原理后,选择哪种锁就得看对应适用场景决定。

最后提一个 Synchronzied 避坑点(美团大佬分享):如果你的系统有很明确的 高低峰期,不建议使用 Synchronized,可以考虑使用 ReentrantLock。原因是 上面提到过 Synchronized 锁的膨胀是不可逆的,导致一旦经历了高峰期后就一直是重量级锁,性能也会由此一直达到一个瓶颈上不去了。

来源:今日头条内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯