繁体   English   中英

如果2个不同的写入和读取线程永远不会同时存在,我是否需要使用volatile

[英]Do I need to use volatile, if 2 different write and read thread will never alive at the same time

通过参考http://www.javamex.com/tutorials/synchronization_volatile.shtml由于附加规则3 ,我不确定在下列情况下是否需要使用volatile关键字

  1. 原始静态变量将由线程A写入。
  2. 线程B将读取相同的原始静态变量。
  3. 线程B只会在线程A“死”后运行。 (“死”表示,线程A的无效运行的最后一个语句已完成)

线程A写入的新值是否会在“死”之后始终提交给主内存? 如果是,是否意味着如果满足上述3个条件,我不需要volatile关键字?

我怀疑在这种情况下需要volatile 根据需要, ArrayList可能会损坏。 由于一个线程可以执行insert和update size成员变量。 之后,另一个线程(不兼容)可能会读取ArrayListsize 如果查看ArrayList源代码,则不ArrayList size声明为volatile。

ArrayList JavaDoc中,只提到ArrayList不能安全地用于多个线程同时访问ArrayList实例 ,但不能让多个线程在不同的时间访问ArrayList实例

让我使用以下代码来解决此问题

public static void main(String[] args) throws InterruptedException {
    // Create and start the thread
    final ArrayList<String> list = new ArrayList<String>();
    Thread writeThread = new Thread(new Runnable() {
        public void run() {
            list.add("hello");
        }
    });
    writeThread.join();
    Thread readThread = new Thread(new Runnable() {
        public void run() {
            // Does it guarantee that list.size will always return 1, as this list
            // is manipulated by different thread?
            // Take note that, within implementation of ArrayList, member
            // variable size is not marked as volatile.
            assert(1 == list.size());
        }
    });
    readThread.join();
}

是的,您仍然需要使用volatile(或其他形式的同步)。

原因是两个线程可以在不同的处理器上运行,即使一个线程在另一个线程开始之前已经完成了很长时间,也不能保证第二个线程在进行读取时会获得最新的值。 如果该字段未标记为volatile并且未使用其他同步,则第二个线程可以获取在其运行的处理器上本地缓存的值。 理论上缓存的值可能在很长一段时间内都是过时的,包括在第一个线程完成之后。

如果使用volatile,则将始终将值写入主内存并从主内存中读取,从而绕过处理器的缓存值。

不,你可能不需要它。 尽管Mark Byers的回答开始相当准确,但它是有限的。 synchronized和volatile不是在线程之间正确传递数据的唯一方法。 还有其他一些较少谈论“同步点”。 具体而言,线程开始和线程结束同步点。 但是,启动线程B的线程必须已经识别出线程A已完成(例如,通过连接线程或检查线程的状态)。 如果是这种情况,则变量不需要是volatile。

可能是的,除非您手动创建内存屏障。 如果A设置变量,并且B决定从某个注册表中取出oit,那么就会出现问题。 因此,您需要一个内存屏障,隐式(锁定,易失性)或显式。

http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.4.4

线程T1中的最终操作与另一个检测到T1已终止的线程T2中的任何操作同步。 T2可以通过调用T1.isAlive()或T1.join()来完成此操作。

因此,可以在不使用volatile的情况下实现目标。

在许多情况下,当存在明显的时间依赖性时,同事正在由人员进行同步,并且应用程序不需要额外的同步。 不幸的是,这不是规则,程序员必须仔细分析每个案例。

一个例子是Swing工作者线程。 人们会在工作线程中进行一些计算,将结果保存到变量,然后引发事件。 然后,事件线程将从变量中读取计算结果。 应用程序代码不需要显式同步,因为“引发事件”已经进行了同步,因此工作线程的写入在事件线程中是可见的。

一方面,这是一种幸福。 另一方面,许多人不理解这一点,他们省略了同步只是因为他们从未想过这个问题。 他们的节目恰好是正确的......这一次。

如果线程A在线程B开始读取之前肯定死亡,则可以避免使用volatile

例如。

public class MyClass {

   volatile int x = 0;

   public static void main(String[] args) {

      final int i = x;
      new Thread() {
         int j = i;
         public void run() {
            j = 10;
            final int k = j;
            new Thread() {
               public void run() {
                  MyClass.x = k;
               }               
            }.start();
         }
      }.start();
   }
}

但问题是,无论Thread启动哪个线程B,现在都需要线程A写入的值已经改变,并且不使用自己的缓存版本。 最简单的方法是让线程A生成线程B.但是如果线程A在生成线程B时没有别的事情可做,那么这似乎有点无意义(为什么不使用相同的线程)。

另一个替代方案是,如果没有其他线程依赖于此变量,则可能线程A可以使用volatile变量初始化局部变量,执行它需要执行的操作,然后最终将其局部变量的内容写回volatile变量。 然后,当线程B启动时,它从volatile变量初始化其局部变量,然后仅从其局部变量读取。 这应该大大减少保持volatile变量同步所花费的时间。 如果这个解决方案似乎不可接受(因为其他线程写入volatile变量或其他),那么你肯定需要声明变量volatile。

暂无
暂无

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

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