美文网首页
线程安全-可见性

线程安全-可见性

作者: 三不猴子 | 来源:发表于2019-05-06 20:42 被阅读0次
    共享变量在线程间不可见的原因
    • 线程的交叉执行
    • 重排序结合线程交叉执行
    • 共享变量更新后的值没有在工作内存与主内存间及时更新
    1. 使用synchronized的来保证可见性

    使用synchronized的两条规定:

    • 线程解锁前,必须把共享变量的最新值刷新到主内存
    • 线程加锁锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意加锁与解锁是同一把锁)
    1. volatile 来实现可见性
      通过加入内存屏障和禁止重拍讯优化来实现可见性。
    • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
    • 对volatile变量进行读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。

    也就是说使用volatile关键字在读和写操作时都会强迫从主内存中获取变量值。

    下图是使用volatile写操作的示意图

    image

    使用volatile写操作前会插入一条StoreStore指令来禁止在volatile写之前的普通写对volatile写的指令重排序优化,在写之后会插入一条StoreLoad屏障指令来防止上面的volatile写操作和下面可能有的读或者写进行指令重排序。

    下图是volatile读操作示意图

    image
    • volatile操作都是cpu指令级别的
      下面看一段演示代码
    @Slf4j
    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
        }
    }
    
    

    我们多次运行个这段代码,发现结果并不是我们预期5000,volatile只能保证可见性并不能保证原子性。

    通常来说使用volatile需要具备两个条件

    • 对变量写操作不依赖当前值
    • 该变量没有包含在其他变量的所在的式中

    所以volatile非常适合用作状态标记量,比如做为线程是否被初始化。还有就是用double check 我之前的博客就提到的单例模式中就使用了volatile来做double check 双重检查实现单例。


    image

    相关文章

      网友评论

          本文标题:线程安全-可见性

          本文链接:https://www.haomeiwen.com/subject/vvcjoqtx.html