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

发布于 2024-12-11 08:47:33 字数 1046 浏览 0 评论 0原文

我试图用一个例子来说明 易失性 的使用和重要性,如果省略 易失性 ,实际上不会给出好的结果。

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

这是代码:

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());
    }
}

I'm trying to illustrate the use and importance of volatile with an example that would really not give a good result if volatile was omitted.

But I'm not really used to using volatile. The idea of the following code is to cause an infinite loop if volatile is omitted, and be perfectly thread-safe if volatile is present. Is the following code thread-safe? Do you have any other realistic and short example of code that uses volatile and would give an obviously incorrect result without it?

Here's the code:

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());
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

冬天旳寂寞 2024-12-18 08:47:33

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

这是我的版本:

    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 之前; A 中的写入对 B 可见。

Victor is right, there are issues with your code: atomicity and visibility.

Here's my edition:

    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;
    }

}

If a thread observes that stopped==true, it's guaranteed that the work completes and the result is visible.

There is a happens-before relation from volatile write to volatile read (on the same variable), so if there are two threads

   thread 1              thread 2

   action A
       |
 volatile write  
                  \
                     volatile read
                          |  
                       action B

action A happens-before action B; writes in A are visible by B.

甜宝宝 2024-12-18 08:47:33

对我来说,以令人信服的方式说明并发问题总是很困难:好吧,发生在之类的事情都很好,但为什么要关心呢?真的有问题吗?有很多很多写得很糟糕、同步很差的程序——但它们在大多数时间仍然有效。

我曾经在“大部分时间都有效 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 秒)才能注意到任何< /em> 改变。

使用易失性,您可以:

Waiting  1319229288280
stopping 1319229298281
stopped  1319229298281
Delay: 0

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

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

希望有帮助。

It was always hard to me to illustrate concurrency problems in a convincing way: well, fine, it's all nice about happens-before and stuff, but why should one care? Is there a real problem? There are lots and lots of poorly written, poorly synchronized programs—and they still work most of the time.

I used to find a resort in a "works most of the time VS works" rhetoric—but, frankly, it's a weak approach. So what I needed is an example which would make difference obvious—and, preferably, painful.

So here is a version which actually does show the difference:

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));
    }
}

A simple run shows:

Waiting  1319229217263
stopping 1319229227263
Waiting  1319229242728
stopped  1319229242728
Delay: 15

That is, it takes more than ten seconds (15 here) for a running thread to notice there was any change.

With volatile, you have:

Waiting  1319229288280
stopping 1319229298281
stopped  1319229298281
Delay: 0

that is, exiting (almost) immediately. The resolution of currentTimeMillis is around 10ms, so the difference is more that 1000 times.

Note it was Apple's version of (ex-)Sun JDK, with -server option. The 10-second wait was added in order to let JIT compiler find out that the loop is hot enough, and optimize it.

Hope that helps.

山有枢 2024-12-18 08:47:33

进一步简化 @Elf 示例,其中其他线程永远不会获得其他线程更新的值。删除 System.out.println 因为 println 中有同步代码,而 out 是静态的,这可以帮助其他线程获取标志变量的最新值。

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();
  }
}

Simplifying @Elf example further, where the other thread will never get the value which was updated by other thread. Removing System.out.println as there is synchronized code inside println and out is static, somehow that helps the other thread to get the latest value of flag variable.

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();
  }
}
浮华 2024-12-18 08:47:33

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

To illustrate the importance of the volatile keyword when it comes to concurrency, all you need to do is make sure that the volatile field is modified and read in a separate threads.

陪你搞怪i 2024-12-18 08:47:33

更新 我的答案是错误的,请参阅来自reputable的答案。


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

通过检查 getCount 方法内的 stopped 易失性来确保 count 值对主线程的可见性。这就是并发实践书中所谓的搭载同步

UPDATE My answer is wrong, see answer from irreputable.


It's not thread-safe, since access to count is not there's only one writer thread. Should there be another writer thread, value of count would be become inconsistent to the number of updates.

Visibility of count value to main thread is ensured by checking stopped volatile inside getCount method. This is what is called piggybacking on synchronization in Concurrency in practice book.

浅沫记忆 2024-12-18 08:47:33

错误的代码,如果 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

Wrong code with which we cannot assume x = 1 also if y is already 2:

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

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

Example of use of volatile keyword:

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.
    }
  }
}

Source: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文