Java 如果两个不同的写线程和读线程永远不会同时激活,我需要使用volatile吗

Java 如果两个不同的写线程和读线程永远不会同时激活,我需要使用volatile吗,java,multithreading,Java,Multithreading,通过引用,我不确定在以下情况下是否需要使用volatile关键字,由于附加规则3。 原始静态变量将由线程A写入 线程B将读取相同的原语静态变量 只有在线程A“失效”后,线程B才会运行。(“dead”表示线程A的void run的最后一条语句已完成) 线程A写入的新值在“死”后是否会始终提交到主内存?如果是,是否意味着如果满足上述3个条件,我不需要volatile关键字 我怀疑在这种情况下是否需要volatile。如果需要,则可能会损坏。因为一个线程可以执行插入和更新size成员变量。稍后,另一个

通过引用,我不确定在以下情况下是否需要使用
volatile
关键字,由于附加规则3。

  • 原始静态变量将由线程A写入
  • 线程B将读取相同的原语静态变量
  • 只有在线程A“失效”后,线程B才会运行。(“dead”表示线程A的void run的最后一条语句已完成)
  • 线程A写入的新值在“死”后是否会始终提交到主内存?如果是,是否意味着如果满足上述3个条件,我不需要
    volatile
    关键字

    我怀疑在这种情况下是否需要
    volatile
    。如果需要,则可能会损坏。因为一个线程可以执行插入和更新
    size
    成员变量。稍后,另一个线程(不是同时)可能会读取
    ArrayList
    size
    。如果查看
    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();
    }
    
    publicstaticvoidmain(String[]args)抛出InterruptedException{
    //创建并启动线程
    最终ArrayList=新ArrayList();
    Thread writeThread=新线程(new Runnable(){
    公开募捐{
    添加(“你好”);
    }
    });
    writeThread.join();
    线程readThread=新线程(new Runnable(){
    公开募捐{
    //它是否保证list.size始终返回1,与此列表相同
    //是由不同的线程操作的吗?
    //请注意,在ArrayList的实现中,成员
    //变量大小未标记为volatile。
    断言(1==list.size());
    }
    });
    readThread.join();
    }
    
    是的,您仍然需要使用volatile(或其他形式的同步)

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


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

    可能是,除非手动创建内存屏障。如果A设置了变量,而B决定从某个注册表获取oit,则会出现问题。因此,您需要一个mmemory屏障,隐式(锁,volatile)或显式。

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

    例如

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


    另一种选择是,如果没有其他线程依赖于此变量,那么线程A可能可以使用volatile变量初始化局部变量,执行它需要执行的操作,然后最后将其局部变量的内容写回volatile变量。然后,当线程B启动时,它会从volatile变量初始化其局部变量,然后只从其局部变量读取。这将大大减少保持易失性变量同步所花费的时间。如果此解决方案看起来不可接受(因为其他线程写入volatile变量或其他什么),那么您肯定需要声明volatile变量。

    否,您可能不需要它。尽管马克·拜尔斯的回答相当准确,但它是有限的。synchronized和volatile不是在线程之间正确传递数据的唯一方法。还有其他一些很少被提及的“同步点”。具体来说,线程开始和线程结束是同步点。但是,启动线程B的线程必须已识别线程A已完成(例如,通过连接线程或检查线程状态)。如果是这种情况,变量不需要是可变的。

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

    因此,不使用volatile就可以实现您的目标

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

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

    一方面,这是一种幸福。另一方面,许多人不理解这一点,他们忽略了同步,只是因为他们从未考虑过这个问题。他们的节目
    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();
       }
    }