JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

Java面试必考问题:如何保证多线程的有序性

wys521 2025-01-21 22:15:48 精选教程 32 ℃ 0 评论

前文介绍了Java内存模型(JMM: Java Memory Model)的设计始终围绕着多线程并发的三个重要特性:原子性(Atomicity)可见性(Visibility)有序性(Ordering)。在并发编程中,我们要始终注意如何处理这三个特性的问题。

本文重点讨论如何保证多线程的有序性。一句话说明有序性规则:如果在本线程内观察,所有的操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。

有序性规则主要体现在两个场景:

  • 线程内:从单个线程来看,指令会按照类似“串行”(as-if-serial)的方式顺序执行;
  • 线程外:这个线程“观察”其他线程并发地执行非同步代码时,由于指令重排序优化,代码有可能交叉执行。

在Java内存模型中,允许编译器和处理器对指令进行重排序(Reordering),但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

指令重排序

在执行程序时为了提高性能,编译器和处理器常常会对指令做重排序。指令重排序分为三种类型:

  1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
  2. 指令级并行的重排序。现代处理器采用了指令级并行技术(ILP: Instruction-Level Parallelism)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
  3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

先行发生原则

Java内存模型本身也有一些先天有序性的约束,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 先行发生原则

  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
  • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的 start() 方法先行发生于此线程的每一个动作
  • 线程中断规则:对线程 interrupt() 中断方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测
  • 对象终结规则:一个对象的初始化完成先行发生于它的 finalize() 方法的开始

先行发生原则是我们用来判断数据是否存在竞争、线程是否安全的主要依据。如果操作A先行发生于操作B,那么操作A产生的影响一定能够被操作B观察到。

需要注意的是,由于重排序的因素,一个操作先行发生并不代表这个操作在时间上是先发生的。

看一个例子

有这样两个线程A和B:线程A为变量 a 赋值,赋值后设置标志 isAssigned;线程B通过 isAssigned 判断是否已完成赋值,如果完成那么就利用变量 a 来计算。

在线程A中,由于两个赋值语句之间没有数据依赖性,因此处理器可能会对指令重排序,调换两个指令的顺序,在单线程内不会对结果造成影响,是不违背happens-before规则的。但是可能会破坏多线程语义。

如果两个线程以上图的顺序运行,那么就可能会出现:线程B在变量被赋值前,就先使用变量进行计算。

保证有序性的方法

在 Java 中,可以使用 synchronized volatile 来保证多线程之间操作的有序性。

(1)volatile 关键字会禁止指令重排序。对于上面的例子,可以将 isAssigned 定义为 volatile 变量。

volatile boolean isAssigned = false;

这样线程A中 对变量a的赋值语句,就不会排序到 isAssigned = true 之后。

(2)synchronized 关键字通过互斥保证同一时刻只允许一条线程操作。

我们可以把线程A和线程B中的相关方法或代码块,定义为同步方法或同步代码块。

由于线程A的代码是同步方法,在线程A获取锁以后,执行过程中不会被线程B打断,这样就保证了 a=1isAssigned=true 这两个指令会一起完成。不会出现线程B判断赋值已完成,但实际上 a 尚未完成赋值的情况出现。

总结

为了充分利用现代处理器的能力,编译器和处理器都可以对指令重排序,但有可能会破坏多线程运行的语义。为了保证程序执行的正确性,在JMM的先行发生原则基础上,使用 synchronized volatile来确保多线程之间操作的有序性。

我会持续更新关于物联网、云原生以及数字科技方面的文章,用简单的语言描述复杂的技术,也会偶尔发表一下对IT产业的看法,欢迎大家关注,谢谢。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表