Java内存模型

主内存与工作内存

Java内存模型规定了所有的变量都存储在主内存中。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者的交互关系如图所示。

volatile

  1. 保证此变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。
  • volatile在并发运算下并不是安全的:
class Test{
    public static volatile int race = 0;
    public static void increase() {
        race++;
    }
    private static final int THREADS_COUNT = 20;
    public static void main(String[] args) {
        Thread[] threads = new Thread[THREADS_COUNT];
        for (int i = 0; i < THREADS_COUNT; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        increase();
                    }}
            });
            threads[i].start();
        }
// 等待所有累加线程都结束
        while (Thread.activeCount() > 1)
            Thread.yield();
        System.out.println(race);
    }
}

输出的结果是小于200000的数字,因为volatile的可见性,所以在拿到race值的时候是正确的,但是在进行race++的时候其他线程可能已经把race++后的值同步到主存中,所以当前线程同步回主存的race值会小。

  1. 禁止指令重排序
    对volatile变量进行赋值的时候,转换为汇编语言会有一个lock的操作(内存屏障:重排序时不能把后面的指令重排序到内存屏障之前的位置),此操作将工作内存的变量写入主存,并使别的工作内存无效,所以可保证volatile的可见性。lock指令把修改同步到主存中,意味着所有之前的操作都已经执行完成,所以形成了“指令重排序无法越过内存屏障”的效果。

先行发生原则(Happens-Before)

  • 程序次序规则:在一个线程内,按照控制流顺序,书写在前面的操作先行发生于书写在后面的操作。
  • 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。
  • volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
  • 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。通过join(),isAlive()方法返回值等检测线程是否已经终止。
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread::interrupted()方法检测到是否有中断发生。
  • 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
  • 传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

你可能感兴趣的:(Java内存模型)