概述
指一个线程对主内存的修改,可以及时地被其他线程观察到。
导致共享变量在线程间不可见的原因有:
- 多线程交叉进行。
- 重排序结合多线程运行。
- 共享变量更新后的值未及时进行工作内存与主内存的更新。
可见性之synchronized关键字
Java内存模型对synchronized关键字有两条规定。
- 线程解锁前必须把共享变量的最新值刷新到主内存。
- 线程加锁时,将清空工作内存中共享变量的值。那么工作内存需要时只能从工作内存中取值。
工作原理:此时的加锁和解锁用的是一个锁,即多线程之间共享synchronized一个锁。
可见性之volatile关键字
工作原理:通过加入内存屏障
和禁止重排序优化
来实现,即通过内存屏障
保证了禁止重排序优化
内存屏障
从以下两点进行说明:
- 对volatile变量进行写操作时,会在写操作后加入一条store屏障指令,将本地工作内存中的值刷新到主内存。
- 对volatile变量进行读操作时,会在读操作前加入一条load屏障指令,从主内存读取共享变量的值。
即强迫从主内存中的共享变量进行交互。
volatile写原理:
volatile读原理:
示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 4j
public class CountExample4 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static volatile int count = 0;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count++;
// 1、count
// 2、+1
}// 3、count
}
多次运行结果:仍然无法保证线程安全
两次测试结果如下:
分析:
volatile修饰的count进行++时,分为三步进行:
- 取出count
- count+1
- 写入count
已知在第一步的取数时,取出的都是主内存的最新值。但是写入的时候就可能发生问题。 当多个线程进行操作时,同时取出主内存中的count
,并执行+1,然后同时写入主内存,那么导致请求总数永远比clientcount
值小,即volatile关键字只能保证可见性,但并具有原子性,不能保证线程安全!
volatile使用的场景
场景需具备的条件:
- 对变量的写操作不依赖与当前值
- 该变量没有包含在具有其他变量的不变的式子中。
因此,
volatile
适合作为状态标记量(boolean型)- 适用于
doublecheck
场景。(例子会在之后手记中添上……)