在另一个线程读取它时更改Java列表引用

时间:2016-06-28 21:59:43

标签: java multithreading

我无法找到这个问题的明确答案,如果在别处得到很好的解答,请道歉。

想象一下,我有类似的东西:

Class SomeClass
{
  List<String> strings = new LinkedList<>();


  public void updateList(List<String> newStrings) {
    strings = newStrings;
  }

  public List<String> getMatchingStrings(String pattern) {
    List<String> matches = new LinkedList<>();
    for (String s : strings) {
      if (matchesPattern(s, pattern)) { //Assume this just works
        matches.add(s);
      }
    }
    return matches;
  }

}

假设另一个线程在某个线程正在调用updateList(...)时调用getMatchingStrings(),那么会出现&#34;问题&#34;?

预期的行为是当前执行getMatchingStrings()的线程将在旧列表上运行,后续调用将在更新列表上运行。

感谢任何帮助!

3 个答案:

答案 0 :(得分:1)

有不安全的出版物&#39;在您的代码中(没有&#39;发生 - 在订单之前&#39;写入列表字段和读取列表字段之间)。使用被阻止的&#39;同步&#39;或者非阻止的“易变”的或者&#39; AtomicReference&#39;:

// with 'synchronized'
class SomeClass0 {
    List<String> strings = new LinkedList<>();

    public synchronized void updateList(List<String> newStrings) {
        this.strings = newStrings;
    }

    public synchronized List<String> getMatchingStrings(String pattern) {
        List<String> matches = new LinkedList<>();
        for (String s : strings) {
            if (matchesPattern(s, pattern)) { //Assume this just works
                matches.add(s);
            }
        }
        return matches;
    }
}

// with 'volatile'
class SomeClass1 {
    volatile List<String> strings = new LinkedList<>();

    public void updateList(List<String> newStrings) {
        this.strings = newStrings;
    }

    public List<String> getMatchingStrings(String pattern) {
        List<String> localCopy = this.strings;
        List<String> matches = new LinkedList<>();
        for (String s : localCopy) {
            if (matchesPattern(s, pattern)) { //Assume this just works
                matches.add(s);
            }
        }
        return matches;
    }
}

// with 'AtomicReference'
class SomeClass2 {
    AtomicReference<List<String>> strings = 
                        new AtomicReference<>(new LinkedList<>());

    public void updateList(List<String> newStrings) {
        this.strings.set(newStrings);
    }

    public List<String> getMatchingStrings(String pattern) {
        List<String> localCopy = this.strings.get();
        List<String> matches = new LinkedList<>();
        for (String s : localCopy) {
            if (matchesPattern(s, pattern)) { //Assume this just works
                matches.add(s);
            }
        }
        return matches;
    }
}

答案 1 :(得分:0)

如果你的意思是会有运行时异常,答案是否定的。迭代字符串的代码将使用迭代器调用时引用的任何内容。

迭代时字符串引用是否发生变化无关紧要。可以这样想,

Iterator<String> i = strings.iterator();
strings = new List<String>();
while (i.hasNext()) {
 ...
 i.next();
}

这段代码很好。字符串指向的对象没有改变。我们只将字符串引用更改为指向其他位置。迭代器仍然指向赋值之前字符串引用的原始对象。

编辑:下面的一些评论说原来的例子是不安全的出版物。我不相信。不安全的发布是关于允许线程B访问线程A中构造的对象 - 线程A是不安全地将对象发布到线程B。

但是,在上面的示例中,没有关于相关字段的对象构造。据我们所知,这两个列表都是以线程安全的方式完全构建的。只有参考作业。 Java中的对象赋值是原子的,来自Java语言规范,

  

“当线程使用变量的值时,   它获得的值实际上是存储在变量中的值   该线程或其他一些线程。即使程序也是如此   不包含正确同步的代码。例如,如果两个   线程将对不同对象的引用存储到同一引用中   值,该变量随后将包含对一个的引用   对象或其他对象,而不是对其他对象的引用或   参考值损坏。 (有一个特殊的例外,长期和   双重价值;见§17.4。)

现在,如果传递给updateList()的对象没有安全构建,OP的代码中可能会出现不安全的发布,但我不打算假设代码中可能存在的错误可能无法看到。

答案 2 :(得分:-1)

Jeffrey是正确的 - 这里没有不安全的发布,因为在任何时候都没有一个线程构造对象而另一个线程可以读取它(该实例受updateList方法调用的保护)。然而,存在访问不一致的可能性很大 - 例如您最终迭代的列表可能不是您期望的列表,即使您先调用updateList ,因为没有明确的内存屏障。在这种情况下,JVM可能会在运行时发生奇怪的事情。

假设最终被迭代的列表(再次,这可能不是您期望的列表)不为空,在迭代发生时没有被修改,正确实现等等,那么这个“模式”不会导致例外。也就是说,它不应该被使用 - 你需要执行同步,并且(可能)用volatile标记局部变量,如果这将在多线程场景中使用。