理解volatile的保证可见性与不保证原子性

可见性

指令执行机制

CPU只对寄存器中的数据进行计算,为了加快寄存器和内存的数据交换,会有一个缓存区,寄存器与缓存区交换速度更快。数据从缓存区写回内存中这个动作由系统决定。所以会存在某一数据被修改,却没有被立即同步到内存中,导致其他线程拿到的值是过期值。而加了volatile则相当于告诉计算机将这个值立即写回内存,对其他线程可见。

不保证原子性

volatile字段可以保证线程拿到了最新值,即get操作是原子性的,但无法保证getAndOperate操作的原子性。因为在操作过程中会存在中间状态,这时该值可能已经过期。例如对于volatile a,a++看似只是一条语句,却是三步:

  1. b=a

理解volatile的保证可见性与不保证原子性_第1张图片
将值a和1分别写入寄存器

  1. b=b+1

理解volatile的保证可见性与不保证原子性_第2张图片
累加器计算两个寄存器的值,写入其中一个寄存器

  1. a=b
    将计算结果从寄存器写回内存

所以在执行第二步的指令时,a的值可能已经过期,第三步将一个基于可能过期的值得到的计算结果写回内存。所以原子性无法保证。
下面用代码加以体现:

 private volatile int  count = 0;

    @Test
    public void volatileTest() throws InterruptedException {
        for (int i = 0; i<20000;i++){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    count++;
                }
            });
            thread.start();
        }
        Thread.sleep(4000L);
        System.out.println(count);
    }

考虑到可能线程还没执行,就已经执行了System.out.println(count),不具有说服性,所以加了休眠时间,等待其他线程执行完,利用CountDownLatch可以更好的实现等待:

    @Test
   public void volatileTest2() throws InterruptedException {
       final CountDownLatch done = new CountDownLatch(80000);
       for (int i = 0; i<80000;i++){
           Thread thread = new Thread(new Runnable() {
               @Override
               public void run() {
                       count++;
                       done.countDown();
               }
           });
           thread.start();
       }
       done.await();
       System.out.println(count);

   }

多次执行,volatileTest2()输出值有79987,79983,79988等,可见volatile并不能保证原子性。

要想保证原子性,无论是通过锁亦或是CAS重试,都必须保证对资源的独占,这样才能保证原子性。

你可能感兴趣的:(Java,并发编程,Java基础)