我无法找到这个问题的明确答案,如果在别处得到很好的解答,请道歉。
想象一下,我有类似的东西:
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()
的线程将在旧列表上运行,后续调用将在更新列表上运行。
感谢任何帮助!
答案 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
标记局部变量,如果这将在多线程场景中使用。