我们前面说过由于存在线程本地内存和主内存的原因,再加上重排序,会导致多线程环境下存在可见性的问题。那么我们正确使用同步、锁的情况下,线程A修改了变量a何时对线程B可见?
我们无法就所有场景来规定某个线程修改的变量何时对其他线程可见,但是我们可以指定某些规则,这规则就是happens-before,从JDK 5 开始,JMM就使用happens-before的概念来阐述多线程之间的内存可见性。也就说这仅仅是一种规则描述,而不是技术实现。
因此happens-before不仅仅局限于volatile,而是针对整个Java的,从JDK 5开始,Java使用新的JSR-133内存模型。JSR-133使用happens-before的概念来阐述操作之间的内存可见性。在JMM中,如果一 个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关 系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
(1)程序顺序规则 1.不能改变程序的执行结果(在单线程环境下,执行的结果不变.) 2.依赖问题, 如果两个指令存在依赖关系,是不允许重排序 例如,这里的c=a*b一定要求前两个赋值完成才可以执行,但是这里并没要求b=1要早与a=1。
- void test(){
- int a=1; a
- int b=1; b
- int c=a*b; c
- }
描述就是“b happens before c”。 需要注意的是,这个的含义是c执行的时候b的结果已经是可见的了,而不是规定指令执行的顺序。
(2)传递性规则 如果A happens-before B,且B happens-before C,那么A happens-before C (3)volatile变量规则 对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
再看个例子:
- 定义了一个变量
- volatile int a=0;
-
- a=10*2;
- System.out.print(a);
- public class VolatileExample {
- int a=0;
- volatile boolean flag=false;
- public void writer(){
- a=1; 1
- flag=true; //修改 2
- }
- public void reader(){
- if(flag){ //true 3
- int i=a; //1 4
- }
- }
- }
1 happens-before 2 是否成立? 是,为什么?这个是volatile导致的一条规则,记住就行了 。
3 happens-before 4 是否成立 是
然后有 2 happens -before 3 ->volatile规则
所以最终 1 happens-before 4 ; i=1成立
(4)监视器锁规则 对一个锁的解锁,happens-before于随后对这个锁的加锁。例如下面对x进行两次加锁,则前一个解锁一定早于下一个加锁操作。
- int x=10;
- synchronized(this){
- //后续线程读取到的x的值一定12
- if(x<12){
- x=12;
- }
- }
start规则 这里其实也是为子线程的初始化提供条件。
- int x = 0;
- Thread t1 = new Thread(() -> {
- //读取x的值 一定是20
- if (x == 20) {
-
- }
- });
- x = 20;
- t1.start();
- }
Join规则
- public class Test{
- int x=0;
- Thread t1=new Thread(()->{
- x=200;
- });
- t1.start();
- t1.join(); //保证结果的可见性。
- //在此处读取到的x的值一定是200.
- }