浅谈JMM

java内存模型

1.导致可见性的原因是缓存:

有序性的原因是编译优化,合理的方案应该是“按需禁用缓存和编译优化”。java的内存模型是个很复杂的规范,从程序员的角度:java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法->volatile,synchronized,final三个关键字,以及六项Happens-Before规则。

2-volatile

volatile int x=1;//告诉编译器对这个x变量的读写不能使用CPU缓存,必须从内存中读或者写,但是实际运用中的困惑却很多!
public class VolatileExample {
    int x = 0;
    volatile boolean v = false;
    public void writer() {
        x = 42;
        v = true;
    }
    public void reader() {
        if (v) {
            System.out.println("x = " + x);
        }
    }
}
class Test {
    public static void main(String[] args) throws InterruptedException {
        VolatileExample volatileExample = new VolatileExample();
        Thread A = new Thread(volatileExample::writer);
        Thread B = new Thread(volatileExample::reader);
        A.start();
        B.start();
        A.join();
        B.join();
    }
}
//x = 42
解释:线程A执行writer方法,安装volatile语义会把变量“v=true”写到内存中,此时线程B执行reader方法,线程B会从内存中读取变量V,此时x结果为42。(java版本必须保证在1.5之后)
JAVA1.5解决了CPU缓存导致可见性问题,Happens-Before规则。
Happens-Before:前面一个操作的结果对后续的操作是可见的。
约束了编译器的优化的行为,编译器优化必须遵循Happens-Before原则(6个原则)。

1.程序代码片段的顺序性规则

// 以下代码来源于【参考 1】
1class VolatileExample {
 2 int x = 0;
 3 volatile boolean v = false;
 4 public void writer() {
 5   x = 42; //对x进行赋值 写
 6   v = true;//对v进行赋值 写
 7  }
 8 public void reader() {
 9   if (v == true) {//此时需要读v
 10    // 这里 x 会是多少呢?
 11  }
  }
}

代码第5行x=42,对于第6行v=true来讲是可见的。(规则1)称之5 happens-before于6

2.Volatile变量规则

对一个volatile编写的写操作happens-before于后续对这个volatile变量的读操作。(在不关联3的时候,就是禁用缓存的意思。)

3.传递性A happens-before B,& B happens-before C,那么A happens-before C。

浅谈JMM_第1张图片

x=42 happens-before v=true的写操作 规则1:程序的顺序性
v=true 写操作 happens-before v=true的读操作 规则2 : volatile修饰变量的可见性
那么根据规则3 x=42 happens-before v=true的读操作!此时线程B能看到线程A中的x=42!

4.管程中的锁–对一个锁的解锁 happens-before 于后续对这个锁的加锁。

管程:一种通用的同步原语-java实现synchronized(此过程的加锁释放锁(代码块执行完}后)都是jvm帮我们实现的 底层:javap monitor机制)。

5.线程start规则

主线程A在启动子线程B后,子线程B能够看到主线程A再启动B前的操作。

6.线程join规则

指主线程A等待子线程B完成(A调用子线程B的join方法实现),当B完成操作后,主线程A能够看到B对共享变量的操作。
Thread B = new Thread(()->{
  // 此处对共享变量 var 修改
  var = 66;
});
// 例如此处对共享变量修改,
// 则这个修改结果对线程 B 可见
// 主线程启动子线程
B.start();
B.join()
// 子线程所有对共享变量的修改
// 在主线程调用 B.join() 之后皆可见
// 此例中,var==66

假设B在线程A中执行,那么B中对变量var的修改 happens-before A,就是A都知道。

7.线程中断规则:

对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。

8.对象终结规则:

一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
final:
修饰变量的时候,告诉编译器,这个变量生而不变(内地地址)。
在正确的构造函数中没有“逸出”就不会出现问题!
// 以下代码来源于【参考 1】
final int x;
// 错误的构造函数
public FinalFieldExample() { 
  x = 3;
  y = 4;
  // 此处就是讲 this 逸出,
  global.obj = this;
}

思考:此时有可能访问到this还没有初始化完成!

问题:一个共享变量a=3,在一个线程中设置了abc的值a=3,怎么让其他线程看到?跳出这个圈子–其实就是怎么解决缓存问题

1-volatile 修饰a
2-加锁synchronized ,根据 happens-before 原则其他线程在A线程释放锁以后就会看到变量a
3-A线程启动后使用A.join(),后续线程就可以看到
4-直接使用static修饰

你可能感兴趣的:(java并发)