可见性
一个线程对主内存的批改能够及时被其它线程察看到
导致共享变量在线程间不可见的起因
- 线程穿插执行
- 指令重排序加上线程穿插执行
- 共享变量更新后的值没有在工作内存与主存间及时更新
- 保障可见性和原子性
对于可见性
Java
提供了synchonized
和volatile
volatile
通过退出内存屏障和禁止重排序优化来实现, 保障可见性不保障原子性
对volatile
变量进行写操作时, 会在写操作后退出一条store
屏障指令, 将工作内存变量值刷新到主内存。
对
volatile
变量进行读操作时, 会在读操作前退出一条load
屏障指令, 从主内存读取共享变量。
通过下面两点, 任何时候, 不同线程总能看到该变量的最新值. 所有的操作都是
CPU
级别的。
并不是说应用了 volatile
就线程平安了
package com.keytech.task;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class VolatileTest {
private static Integer clientTotal=5000;
private static Integer threadTotal=200;
private static volatile Integer count=0;
public static void main(String[] args) {ExecutorService executorService = Executors.newCachedThreadPool();
Semaphore semaphore=new Semaphore(threadTotal);
for (int i = 0; i < clientTotal; i++) {executorService.execute(()->{
try{semaphore.acquire();
update();
semaphore.release();}catch (Exception e){e.printStackTrace();
}
});
}
executorService.shutdown();
System.out.println("count:"+count);
}
private static void update(){count++;}
}
//count:4988
尽管应用了
volatile
, 然而线程不平安。起因:update
是非原子性的。
private static void update() {
count++; // 分 3 步
//1. 取出以后 count 值
//2.count + 1
//3.count 从新写回主存
}
假如同时有两个线程进行操作,两个线程同时执行到第一步(从内存中读取最新值)失去一样的最新的后果,而后进入第二步(+ 1 操作)并进行第三步(从新写回主存)。只管第一步获取的值是一样的,然而同时将 + 1 后的操作写回主存,这样就会丢掉某个 + 1 的操作,这样就会呈现线程不平安问题
总结
volatile
进行多线程加是线程不平安的, 不适宜计数volatile
不具备原子性
volatile
的应用场景
- 对变量的写操作不依赖以后值
- 该变量没有蕴含在其它变量的不变式子中
- volatile 适宜作为状态的标记量
volatile boolean flag = false;
// 线程 1
context = loadContext();
flag = true;
// 线程 2
while(!flag){sleep();
}
todo(context);