赞
踩
JVM内存屏障指令对Java开发工程师是透明的,是JMM对JVM实现的一种规范和要求。
JMM定义了一套自己的规则,Happens-Before规则(先行发生),并且确保两个Java语句必须存在Happens-Before关系,JMM尽量确保这俩个Java语句之间的内存可见性和指令的有序性。
写操作
,必须先于发生对volatile的读操作
线程 A
先行发生于线程 B
操作,线程 B
操作又先行发生于 线程 C
操作,那么线程 A
必须先行发生于线程 C
操作。start操作先行
发生于这个线程内部的其他任何操作
。具体来说,如果线程A执行线程B的start()
方法,那么线程A 启动线程 B 的 start()方法
,先于发生线程B中的任意操作
。线程B的join()
方法,那么线程B的任意操作,先行发生于线程A所执行的 线程B的join()方法
顺序性规则的具体内容:一个线程内,按照代码顺序书写在前面的操作,先行发生于 书写在后面的操作。
顺序性规则是Java内存模型(JMM)中一个基本的概念,它保证了在一个线程内部观察到的操作执行顺序,会符合程序代码的逻辑顺序。这意味着,在单线程环境下,程序的执行将保持我们书写的指令顺序,不会出现乱序执行的情况。简单来说,如果在代码中先写了操作A,然后是操作B,那么在同一个线程中执行时,A必然会在B之前完成,不会出现B先于A执行的结果。
虽然可能发生重排序
,但是他只对不存在数据依赖代码行,进行指令重排序
int x = 0;
int y = 0;
void method() {
x = 1; // 操作A
y = 2; // 操作B
}
根据顺序性规则,在method
方法内部,操作A(x = 1;
)总是会先于操作B(y = 2;
)执行完成。这意味着,在同一个线程调用method
方法后,任何检查x
和y
值的后续代码都将看到x
为1且y
为2,不可能看到y
已经更新为2而x
还未被设置为1的情况。
volatile的具体内容:对一个volatile变量的的写,先行发生于任意后续对这个volatile变量的读。
volatile的主要作用:
volatile
变量的修改,能够立即刷新到主内存
中,所有其他线程对该变量的访问都会重新从主内存中获取最新值,从而确保了变量的可见性。禁止指令的重排序优化
。具体来说,对volatile变量的写操作,在写后,会有一个内存屏障(Memory Barrier),确保该写操作不会被重排序到之后的读写操作之前;相应的,对volatile变量的读操作前也会有一个内存屏障,确保该读操作不会被重排序到之前的写操作之后。这正是你提到的“对一个volatile变量的写,先行发生于任意后续对这个volatile变量的读。package com.hrfan.thread; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // 每个参与测试的线程都将拥有其独立的实例 @State(Scope.Thread) public class VolatileRecorderDemo { int x = 10; int value = 0; boolean flag = false; public void update() { x = 100; // 代码1 flag = true; // 代码2 } public synchronized void add() { if (flag) { // 代码3 value = x + x; System.out.println(Thread.currentThread().getName() + "- value:" + value); } } @Benchmark public void test(){ // 假设线程1 执行 update(),线程2 执行 add(),代码1 和 代码2 并没有依赖关系,所以 代码1 和 代码2 可能被重排序,他们排序后结果为 // flag = true; // value = 100; // 假设重排序后,线程1 执行了 flag = true; 此时还没有执行 value = 10; 线程2 开始执行 add()方法.此时value的值为 10 // 那么最终结果 value = 20 而不是 200 VolatileRecorderDemo volatileRecorderDemo = new VolatileRecorderDemo(); volatileRecorderDemo.update(); volatileRecorderDemo.add(); } @Test @DisplayName("测试") public void start() { Options opt = new OptionsBuilder() .include(VolatileRecorderDemo.class.getSimpleName()) // 预热3轮 .warmupIterations(3) // 度量5轮 .measurementIterations(5) // 设置线程数,比如设置为4个线程 .threads(200) // fork的JVM实例数量 每轮任务数量 .forks(5) .build(); try { new Runner(opt).run(); } catch (RunnerException e) { throw new RuntimeException(e); } } }
假设线程A执行 update()方法,线程B 执行 add()方法,因为代码1 和代码2并没有依赖关系,所以代码1 和代码2就可能会被重排序,他们重排序后的次序可能为
flag = true; // 代码2
value = 100; // 代码1
线程A执行重排代码后,在完成 代码2 之前(flag = true),假设线程B开始执行 add()方法,将 x 的值进行累加,此时的 x 的值就是 10 而不是100,那么 x 累加完成后的值就是 20。这个不是我们想要的结果,为了获取正确的结果,我们必须阻止代码进行重排序,为以上代码的flag成员属性增加 volatile
修饰,
public class VolatileRecorderDemo { int x = 10; int value = 0; volatile boolean flag = false; public void update() { x = 100; // 代码1 flag = true; // 代码2 } public synchronized void add() { if (flag) { // 代码3 value = x + x; // 代码4 System.out.println(Thread.currentThread().getName() + "- value:" + value); } } }
从前面的顺序性规则,已经知道,如果 代码2的操作为 volatile写
,无论第一个操作是什么都不能重排序
。所以代码1 不会排到 代码2 后面的。
代码3 为读取 flag(volatile)变量
,那么 代码4 就不会被重排序到 代码3 之前。
如果
线程 A
先行发生于线程 B
操作,线程 B
操作又先行发生于线程 C
操作,那么线程 A
必须先行发生于线程 C
操作。
例如,如果线程A修改了一个变量,然后线程B读取了这个变量的值,并且线程B接着修改了另一个变量,线程C随后读取线程B修改的变量值,根据传递性规则,线程A对第一个变量的修改操作在逻辑上必须在C线程读取第二个变量值之前发生,保证了跨线程间操作的正确序列化。
对于一个监视锁的解锁操作先行发生 于 后续对这个监视锁的加锁操作。(同一个线程,解锁操作,必须先于加锁操作之前执行)
在Java内存模型中,监视锁规则规定了对于同一个监视器(Monitor,通常指由synchronized
关键字实现的同步块或方法)的解锁操作,必须先行发生于后续针对该监视器的加锁操作。这意味着,如果线程A解锁了一个监视器(即退出了同步代码块或方法),那么这个解锁操作将发生在任何其他线程B(包括线程A自身)随后成功获取这个监视器锁的操作之前。这一规则确保了以下几点:
public class VolatileRecorderDemo2 { int x = 10; int value = 0; boolean flag = false; public synchronized void update() { value = 100; // 代码1 flag = true; // 代码2 } public synchronized void add() { if (flag) { // 代码3 value = x + x; // 代码4 System.out.println("x = " + x); } } }
先获取锁的线程,读 value 赋值之后,释放锁,那么另外一个线程 再去获取锁的时候,一定能看到对 value赋值的改动。
对于线程的
start操作先行
发生于这个线程内部的其他任何操作
。具体来说,如果线程A执行线程B的start()
方法,那么线程A 启动线程 B 的 start()方法
,先于发生线程B中的任意操作
。简单来说,就是
主线程A
启动子线程B
后,线程B
能看到线程A
在启动操作前的任何操作
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StartRuleDemo { private static final Logger log = LoggerFactory.getLogger(StartRuleDemo.class); private int x = 0; private int y = 0; private Boolean flag = false; @Test @DisplayName("测试start()规则") public void testStartRule() throws InterruptedException { // 创建线程B 先不启动 Thread threadB = new Thread(this::printInfo, "线程B"); // 线程A 先 对数据进行赋值操作 Thread threadA = new Thread(() -> { x = 100; y = 200; flag = true; // 启动线程B threadB.start(); }, "线程A"); threadA.start(); } public void printInfo() { log.error("============================ 线程B打印相关信息 ============================"); log.error("x = {}", x); log.error("y = {}", y); log.error("flag = {}", flag); } }
join()规则的具体内容是:如果
线程A
执行threadB.join()
操作后,并成功返回。那么线程B中的任意操作
,先行发生于线程A的 threadB.Join()
。这意味着,通过调用
join()
,线程A确保了线程B的所有操作都已经完成了,这包括线程B的执行、修改共享变量、资源释放等,所有这些都完全发生在线程A得以继续其后续代码执行之前。这保证了线程间操作的顺序性和数据的一致性,避免了因并发执行可能产生的数据竞争问题。join() 规则 刚好和 start()规则 相反
在Java中,Thread.join()
方法是一个非常重要的同步机制,它允许一个线程等待另一个线程执行完成后再继续执行。
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class StartRuleDemo { private static final Logger log = LoggerFactory.getLogger(StartRuleDemo.class); private int x = 0; private int y = 0; private Boolean flag = false; @Test @DisplayName("测试join()规则") public void testJoinRule() throws InterruptedException { // 创建线程B 先不启动 Thread threadB = new Thread(this::updateThreadB, "线程B"); // 线程A 先 对数据进行赋值操作 Thread threadA = new Thread(() -> { // 启动线程B threadB.start(); try { threadB.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } printInfo(); }, "线程A"); threadA.start(); } public void printInfo() { log.error("============================ 线程A打印相关信息 ============================"); log.error("x = {}", x); log.error("y = {}", y); log.error("flag = {}", flag); } public void updateThreadB(){ this.x = 100; this.y = 200; this.flag = true; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。