1.java内存模型
在计算机中,为了解决主内存的速度跟不上处理器速度的问题,我们给每个处理器添加一级或多级高速缓存(如下图)。但是,每个处理器上缓存的数据如何保证一致性呢?
为了实现缓存数据的一致性,我们计算机中使用了缓存一致性协议。java中也有类似的机制来实现多线程的数据模型。
java的内存模型规定如下:
所有的变量均存在主内存中(这里及后文的变量指所有可能出现竞争的变量:包括成员变量、静态变量,不包括局部变量)
每个线程都有自己的工作内存,线程对变量的操作必须在工作内存中完成。不同线程之间的工作内存相互隔离。
那么这个内存模型在jvm中究竟是如何实现的呢?
主内存:对应堆中的实例对象
工作内存:对应虚拟机栈,虚拟机可能会对这部分内存进行优化,将其放入寄存器或者高速缓存中。
看如下实例。
public class Demo4 { private static int i = 0; public static void main(String[] args) throws InterruptedException { new Thread(()-> { for(int j = 0; j < 1000000; j++) { i++; } System.out.println("thread1 run pass"); }).start(); new Thread(()-> { for(int j = 0; j < 1000000; j++) { i++; } System.out.println("thread2 run pass"); }).start(); Thread.sleep(1000); System.out.println(i); } }
运行结果如下:
thread1 run pass thread2 run pass 1729677
为什么呢?如果您有jvm
的基础,就应该知道,原来自增操作并不是原子操作。可能出现如下图的情况。后面我们将讲解如何解决这个问题。
2.重排序
在编译或者运行期间,有可能会对指令进行重排序。有以下可能:
- java编译器根据对java语义的理解进行重排序。
- 现代处理器可能会对机器指令自主进行重排序。
在单线程的环境下,指令重排序可以在不改变执行结果的前提下优化代码的执行效率。但是多线程下指令重排序可能会导致一些问题。
public class Demo5 { private static int a = 0; private static int b = 0; public static void main(String[] args) { new Thread(() -> { if(b == 1) { if(a == 0) { System.out.println("A"); } if(a == 1) { System.out.println("B"); } } } ).start(); new Thread( () -> { a = 1; b = 1; } ).start(); }
按朴素的理解,上面代码的输出只有可能是"B"或者没有输出。但是上面a,b的赋值可能被重排序
public class Demo5 { private static int a = 0; private static int b = 0; public static void main(String[] args) { new Thread(() -> { if(b == 1) { if(a == 0) { System.out.println("A"); } if(a == 1) { System.out.println("B"); } } } ).start(); new Thread( () -> { b = 1; a = 1; } ).start(); }
因此可能出现结果为"A"的情况。