共享变量在线程中的可见性问题分析

导致共享变量在线程间不可见的原因:
1)线程交叉执行
2)重排序结合线程交叉执行
3)共享变量更新后的值没有在工作内存与主存间及时更新

可见性-synchronized
JVM中关于synchronized的两条规定:
1)线程解锁前,必须把共享变量的最新值刷到主内存
2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值( 注意:加锁和解锁是同一把锁
可见性-volatile
通过加入 内存屏障禁止重排序优化来实现(对于被volatile变量的操作都是直接针对主内存)
1)对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存

2)对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++的操作,可以分为三步:
1、取得count的值; 2、对count进行加1操作; 3、写count的值到主内存中;
上面这三步合起来就不是线程安全的,比如,两个线程可能同时取得count的值,然后,同时进行加1操作,并写回主存,这样就丢掉了一次加1的操作。
即: voliatile不适用于计数的场景。


猜你喜欢

转载自blog.csdn.net/timchen525/article/details/80444514