共享变量在线程中的可见性问题分析
# 导致共享变量在线程间不可见的原因:
- 线程交叉执行
- 重排序结合线程交叉执行
- 共享变量更新后的值没有在工作内存与主存间及时更新
# 可见性-synchronized
JVM中关于synchronized的两条规定:
- 线程解锁前,必须把共享变量的最新值刷到主内存
- 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁是同一把锁)
# 可见性-volatile
通过加入内存屏障和禁止重排序优化来实现(对于被volatile变量的操作都是直接针对主内存)
- 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
- 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
图片未正常显示
# 问题:只利用volatile来修饰计数器,是否能够保证计数器是原子操作的了?答案是不能的。
比如如下代码:(在count前加入volatile修饰)
public class AtomicExample7 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
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 (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count ++;
}
}
输出:
[main] INFO com.example.concurrent.example.count.AtomicExample7 - count:4977、
分析:
在上面的代码中的计数变量count前加入了volatile保证变量count的读和写能够及时的更新到内存中,但是运行出来的结果仍然是非线程安全的,原因是在cout++的操作,可以分为三步:
- 取得count的值;
- 对count进行加1操作;
- 写count的值到主内存中;
上面这三步合起来就不是线程安全的,比如,两个线程可能同时取得count的值,然后,同时进行加1操作,并写回主存,这样就丢掉了一次加1的操作。 即:voliatile不适用于计数的场景。
上次更新: 2023/02/06, 09:35:40