赞
踩
当一个线程仅仅对数据是读取的操作,Java会认为该线程不需要刷新缓存
。当有另一个线程,对数据进行修改时,第一个线程不可见,出现了数据不一致的问题JMM模型
代码演示
public class Volatile { private static int INIT_VALUE = 0; private final static int MAX_LIMIT = 5; public static void main(String[] args) { new Thread(() -> {//负责修改数据 int localValue = INIT_VALUE; while (localValue < MAX_LIMIT) { System.out.println("数值即将更新为" + ++localValue); INIT_VALUE = localValue; try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }, "UPDATER").start(); new Thread(() -> {//新建一个读取数据的线程 int localValue = INIT_VALUE; while (localValue < MAX_LIMIT) { if (localValue != INIT_VALUE) {//当数据发生更改 System.out.println("数据现在为" + INIT_VALUE); localValue = INIT_VALUE; } } }, "READER").start(); } }
结果:
数值即将更新为1
数值即将更新为2
数值即将更新为3
数值即将更新为4
数值即将更新为5
解决方法有两种方法:
给数据总线加锁
阻塞了其他CPU对其他部件访问(如内存)
CPU高速缓存一致性协议
例如 Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的,它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
比如
i = 9 时 :
9 的二进制为1001
执行赋值的操作时必须保证二进制的每一位都赋值成功
不能出现: i = 1000 或者 i = 0001
缓存不一致的问题
比如:
----------Thread-1----------------
init = true 2;
obj = createObj() 1;
----------Thread-2----------------
while(!init){
sleep(100);
}
useTheObj(obj);
----------------------------------
基本数据类型的变量读取和赋值
是保证了其原子性,这些操作不可中断分析
i = 10; 原子性
b = a ; 不满足,1.load a,2. assign b
c++; 不满足,1.load c,2. add ,3. assign c
c = c+1;不满足,1.load c,2. add, 3. assign c
下面以cnt++
举例
单个操作
具备原子性即 load、store、read 和 write 操作可以不具备原子性。
使用volatile关键字
synchronized
final
在一个线程内
,在程序前面的操作先行发生于后面的操作可以通过 interrupted() 方法检测到是否有中断发生
一个共享变量被volatile修饰,具备两层语义
强制对缓存的修改操作立刻写入到主存中,并且会使其他线程的缓存失效
。保证了线程间的可见性不会把后面的指令放在前面,也不会把前面的指令放在后面
。也就是保证了有序性public class Volatile { private static volatile int INIT_VALUE = 0; private final static int MAX_LIMIT = 5; public static void main(String[] args) { new Thread(() -> {//负责修改数据 int localValue = INIT_VALUE; while (localValue < MAX_LIMIT) { System.out.println("数值即将更新为" + ++localValue); INIT_VALUE = localValue; try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } }, "UPDATER").start(); new Thread(() -> {//新建一个读取数据的线程 int localValue = INIT_VALUE; while (localValue < MAX_LIMIT) { if (localValue != INIT_VALUE) {//当数据发生更改 System.out.println("数据现在为" + INIT_VALUE); localValue = INIT_VALUE; } } }, "READER").start(); } }
1. 状态量的标记
volatile boolean flag = true;
while(flag){
//....
}
.....
2. 保证屏障前后的一次性
private boolean volatile init = false ----------Thread-1---------------- obj = createObj() 1; ..... 2; init = true 3; .... 4; .... 5; ----------Thread-2---------------- while(!init){ sleep(100); } useTheObj(obj); ----------------------------------
以下代码可以验证Java的重排序
public class Main { /** * 这是一个验证结果的变量 */ private static int a = 0; /** * 这是一个标志位 */ private static boolean flag = false; public static void main(String[] args) throws InterruptedException { //由于多线程情况下未必会试出重排序的结论,所以多试一些次 for (int i = 0; i < 1000000; i++) { ThreadA threadA = new ThreadA(); ThreadB threadB = new ThreadB(); threadA.start(); threadB.start(); //这里等待线程结束后,重置共享变量,以使验证结果的工作变得简单些. threadA.join(); threadB.join(); a = 0; flag = false; } } static class ThreadA extends Thread { public void run() { a = 1; flag = true; } } static class ThreadB extends Thread { public void run() { if (a == 0 && flag) { System.out.println("ha,a==0"); } } } }
出现了重排序
在 happens-before 原则下
在共享变量与多线程的情况下
,重排序导致线程B对资源的访问出现的BUGCopyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。