繁体   English   中英

说明volatile:这段代码是线程安全的吗?

[英]illustrating volatile : is this code thread-safe?

我想说明的使用和重要性volatile用一个例子,如果这样做真的不给一个好的结果volatile省略。

但我并不习惯使用volatile 如果省略volatile ,则以下代码的想法是导致无限循环,并且如果存在volatile则完全是线程安全的。 以下代码是否是线程安全的? 你有其他任何使用volatile的代码的现实和简短的例子,没有它会给出明显不正确的结果吗?

这是代码:

public class VolatileTest implements Runnable {

    private int count;
    private volatile boolean stopped;

    @Override
    public void run() {
        while (!stopped) {
            count++;
        }
        System.out.println("Count 1 = " + count);
    }

    public void stopCounting() {
        stopped = true;
    }

    public int getCount() {
        if (!stopped) {
            throw new IllegalStateException("not stopped yet.");
        }
        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileTest vt = new VolatileTest();
        Thread t = new Thread(vt);
        t.start();
        Thread.sleep(1000L);
        vt.stopCounting();
        System.out.println("Count 2 = " + vt.getCount());
    }
}

Victor是对的,您的代码存在问题:原子性和可见性。

这是我的版本:

    private int count;
    private volatile boolean stop;
    private volatile boolean stopped;

    @Override
    public void run() {
        while (!stop) {
            count++; // the work
        }
        stopped = true;
        System.out.println("Count 1 = " + count);
    }

    public void stopCounting() {
        stop = true;
        while(!stopped)
           ; //busy wait; ok in this example
    }

    public int getCount() {
        if (!stopped) {
            throw new IllegalStateException("not stopped yet.");
        }
        return count;
    }

}

如果一个线程观察到stopped==true ,则保证工作完成并且结果可见。

从易失性写入到易失性读取(在同一变量上)之间存在一个先发生的关系,因此如果有两个线程

   thread 1              thread 2

   action A
       |
 volatile write  
                  \
                     volatile read
                          |  
                       action B

行动A发生在行动B之前; B中可以看到A中的写入

进一步简化@Elf示例,其他线程永远不会获得由其他线程更新的值。 删除System.out.println因为println中存在同步代码而out是静态的,以某种方式帮助其他线程获取flag变量的最新值。

public class VolatileExample implements Runnable {
   public static boolean flag = true; 


  public void run() {
     while (flag);
  }

  public static void main(String[] args) throws InterruptedException {
    Thread thread = new Thread(new VolatileExample());
    thread.start();
    Thread.sleep(1000L);
    flag = false;
    thread.join();
  }
}

我总是很难用一种令人信服的方式来说明并发性问题:好吧,很好, 以前发生的事情和事情都很好,但为什么要关心呢? 有真正的问题吗? 有很多很多写得不好,程序不合理的程序 - 而且它们大部分时间都在工作。

我曾经在“ 大部分时间工作 VS 作品 ”的修辞中找到一个度假胜地 - 但是,坦率地说,这是一种微弱的方法。 所以我需要的是一个可以明显区别的例子 - 最好是痛苦的。

所以这是一个实际上显示差异的版本:

public class VolatileExample implements Runnable {
    public static boolean flag = true; // do not try this at home

    public void run() {
        long i = 0;
        while (flag) {
            if (i++ % 10000000000L == 0)
                System.out.println("Waiting  " + System.currentTimeMillis());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new VolatileExample());
        thread.start();
        Thread.sleep(10000L);
        flag = false;
        long start = System.currentTimeMillis();
        System.out.println("stopping " + start);
        thread.join();
        long end = System.currentTimeMillis();
        System.out.println("stopped  " + end);
        System.out.println("Delay: " + ((end - start) / 1000L));
    }
}

一个简单的运行显示:

Waiting  1319229217263
stopping 1319229227263
Waiting  1319229242728
stopped  1319229242728
Delay: 15

也就是说,正在运行的线程需要超过十秒 (此处为15)才能注意到有任何更改。

有了volatile ,你有:

Waiting  1319229288280
stopping 1319229298281
stopped  1319229298281
Delay: 0

也就是说,几乎立即退出。 currentTimeMillis的分辨率大约为10ms,因此差异超过1000次。

请注意,这是Apple的(ex-)Sun JDK版本,带有-server选项。 添加了10秒等待,以便让JIT编译器发现循环足够热,并对其进行优化。

希望有所帮助。

错误的代码,如果y已经是2,我们也不能假设x = 1:

Class Reordering {
  int x = 0, y = 0;
  public void writer() {
    x = 1;
    y = 2;
  }

  public void reader() {
    int r1 = y;
    int r2 = x;
  }
}

使用volatile关键字的示例:

class VolatileExample {
  int x = 0;
  volatile boolean v = false;
  public void writer() {
    x = 42;
    v = true;
  }

  public void reader() {
    if (v == true) {
      //uses x - guaranteed to see 42.
    }
  }
}

资料来源: http//www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

更新我的回答是错误的,看到无可争议的回答。


不是 线程安全的,因为 访问 count不是 只有一个编写器线程。 如果有另一个编写器线程, count值将与更新的数量不一致。

通过检查getCount方法中的stopped volatile,可以确保count数值对主线程的可见性。 这就是Concurrency in practice书中的Concurrency in practice称为piggybacking on synchronization

为了说明volatile关键字在并发volatile的重要性,您需要做的就是确保在单独的线程中修改和读取volatile字段。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM