搜索
写经验 领红包

java内存模型是什么(java内存模型的三大特性)

导语:深入理解Java内存模型(JMM)中的可见性

可见性是指当一个线程修改了某个共享变量的值时,其他线程是否能够立即知道这个修改。显然,对于串行程序来说,可见性问题是不存在的。因为你在任何一个操作步骤中修改了某个变量的值,在后续的步骤中读取这个变量的值时,读取的一定是修改后的新值。

但是这个问题存在于并行程序中。如果一个线程修改了某个全局变量的值,那么其他线程未必可以马上知道这个改动。图1.14展示了发生可见性问题的一种可能。如果在CPU1和CPU2上各运行了一个线程,它们共享变量t,由于编译器优化或者硬件优化的缘故,在CPU1上的线程将变量t进行了优化,然后将其缓存在cache中或者寄存器里。在这种情况下,如果在CPU2上的某个线程修改了变量t的实际值,那么CPU1上的线程可能无法知道这个改动,依然会读取cache中或者寄存器里的数据。因此,就产生了可见性问题。外在表现为:变量t的值被修改,但是CPU1上的线程依然会读到一个旧值。可见性问题也是并行程序开发中需要重点关注的问题之一。

图1.14 发生可见性问题的一种可能

可见性问题是一个综合性问题。除上面提到的缓存优化或者硬件优化(有些内存读写可能不会立即触发,而会先进入一个硬件队列等待)会导致可见性问题,指令重排(这个问题将在下一节中详细讨论)及编辑器的优化,也有可能导致一个线程的修改不会立即被其他线程察觉。

下面来看一个简单的例子:

Thread 1 Thread 2

1: r2 = A; 3: r1 = B;

2: B = 1; 4: A = 2;

上述两个线程并行执行,分别有1、2、3、4四条指令。其中指令1、2属于线程1,而指令3、4属于线程2。

从指令的执行顺序上看,r2==2并且r1==1似乎是不可能出现的。但实际上,我们并没有办法从理论上保证这种情况不出现。因为编译器可能将指令重排成:

Thread 1 Thread 2

B = 1; r1 = B;

r2 = A; A = 2;

在这种执行顺序中,就有可能出现刚才看似不可能出现的r2 == 2并且r1 == 1的情况。

这个例子就说明,在一个线程中观察另外一个线程的变量,它们的值是否能观测到、何时能观测到是没有保证的。

再来看一个稍微复杂一些的例子:

Thread 1 Thread 2

r1 = p; r6 = p;

r2 = r1.x; r6.x = 3;

r3 = q;

r4 = r3.x;

r5 = r1.x;

这里假设在初始时,p == q 并且 p.x == 0。对于大部分编译器来说,可能会对线程1进行向前替换的优化,也就是r5 = r1.x这条指令会被直接替换成r5 = r2。由于它们都读取了r1.x,又发生在同一个线程中,因此,编译器很可能认为第2次读取是完全没有必要的。因此,上述指令可能会变成:

Thread 1 Thread 2

r1 = p; r6 = p;

r2 = r1.x; r6.x = 3;

r3 = q;

r4 = r3.x;

r5 = r2;

现在思考这么一种场景。假设线程2中的r6.x = 3发生在r2 = r1.x和r4 = r3.x之间,而编译器又打算重用r2来表示r5,那么就有可能出现非常奇怪的现象。你看到的r2是0,r4是3,但是r5还是0。因此,如果从线程1来看就是:p.x的值从0变成了3(因为r4是3),接着又变成了0(这是不是算一个非常怪异的问题呢?)。

内容摘自《实战Java高并发程序设计(第3版)》,本书适合:所有java从业人员,所有数据库工程师阅读。

逻辑顺畅。全书脉络清晰,从Java高并发程序的设计基础开始由底层原理落实到具体案例,环环相扣,完整流畅。结构严谨。总体上循序渐进,逐步提升。每一章都各自有鲜明的侧重点,有利于读者快速抓住重点。

实用性强。本书注重实战,采用了理论结合实践的编写方法,给重要的知识点都安排了代码实例,帮助读者在工作中实战应用。

免责声明:本站部份内容由优秀作者和原创用户编辑投稿,本站仅提供存储服务,不拥有所有权,不承担法律责任。若涉嫌侵权/违法的,请反馈,一经查实立刻删除内容。本文内容由快快网络小茜创作整理编辑!