我正在实践中阅读Java Concurrency,并遇到以下代码片段。我认为synchronized
的使用是为了提高可见性(让调用generator.get()
的线程查看最新的primes
),因为任务PrimeGenerator
由一个线程执行,而内部primes
与其他线程不共享线程。
我对吗?
清单7.1。使用易失性字段来保持取消状态。
@ThreadSafe
public class PrimeGenerator implements Runnable {
@GuardedBy("this")
private final List<BigInteger> primes
= new ArrayList<BigInteger>();
private volatile boolean cancelled;
public void run() {
BigInteger p = BigInteger.ONE;
while (!cancelled) {
p = p.nextProbablePrime();
synchronized (this) {
primes.add(p);
}
}
}
public void cancel() {
cancelled = true;
}
public synchronized List<BigInteger> get() {
return new ArrayList<BigInteger>(primes);
}
}
清单7.2。产生一秒钟的素数。
List<BigInteger> aSecondOfPrimes() throws InterruptedException {
PrimeGenerator generator = new PrimeGenerator();
new Thread(generator).start();
try {
SECONDS.sleep(1);
} finally {
generator.cancel();
}
return generator.get();
}
synchronized
用于并发,而不是可见性。在单线程中,synchronized
是多余的在您的示例中,仅创建一个Thread
。因此,是的,您可能认为不需要使用synchronized
。但是,如果运行两个Thread
:线程A仍在调用PrimeGenerator::run
而线程B在PrimeGenerator::get
中该怎么办?
那时发生的事情是B正在构造一个新的集合,并迭代所有值以将它们复制到新的集合中。同时,A正在添加新的值。 B的预期结果是什么?你怎么知道的?最好的情况是get
对其他线程说“一直等到我完成复制之前再修改primes
”。
如果仅同步primes.add(p)
,将不会解决任何问题,因为修改primes
本身就是问题。如果仅同步get
,则add
的内部将破坏primes
集合。
所以两个synchronized
都是必需的。只需考虑在7.2清单示例之外查看大图。如果PrimeGenerator
仅在7.2列表中使用,则不会发生任何问题,但这是不可重用的。