Java中带有System.out.println()的volatile
我在许多帖子中读到过这样的观点,即Java中带有System.out.println()的volatile,java,multithreading,thread-safety,system.out,Java,Multithreading,Thread Safety,System.out,我在许多帖子中读到过这样的观点,即System.out.println()使代码在某种程度上具有线程安全性,因此为了模拟竞争,应该从代码中删除System.out.println() 现在,PrintStream的write()方法在写入流之前会在this上同步,因此每次调用write()时,都会保持并释放锁 write()PrintStream的方法 public void write(int b) { try { synchronized (this) { //acq
System.out.println()
使代码在某种程度上具有线程安全性,因此为了模拟竞争,应该从代码中删除System.out.println()
现在,PrintStream
的write()
方法在写入流之前会在this
上同步,因此每次调用write()
时,都会保持并释放锁
write()
PrintStream的方法
public void write(int b) {
try {
synchronized (this) { //acquires a lock
ensureOpen();
out.write(b);
if ((b == '\n') && autoFlush)
out.flush();
}
}
catch (InterruptedIOException x) {
Thread.currentThread().interrupt();
}
catch (IOException x) {
trouble = true;
}
}
但这会影响种族的行为吗
假设:
boolean flag = true;
Thread 1:
System.out.println();
flag = false;
System.out.println();
Thread 2:
System.out.println();
while(flag){
System.out.println();
}
现在,如果我们看到两个线程都在锁定同一个对象,即this
(PrintStream),现在由于标志
包含在两个系统输出
之间,这两个系统输出获取并释放相同的锁,那么标志
值将从缓存中刷新,并在其他线程可以看到它的内存中更新
因此,由于模拟竞争是困难的,因此理论上是否有可能该代码是线程安全的,并且线程2将看到对标志所做的更改
如果是,那么可以使用System.out.println()实现与volatile
相同的效果代码>?JLS在该主题上有以下说明:监视器上的解锁发生在该监视器上的每个后续锁定之前。
()
在JLS中,您可以阅读“之前发生”等的确切定义,但这基本上意味着线程在解锁锁之前发生的每一次读写都会被另一个获取锁的线程看到(小心!如果有线程3在未获取锁的情况下写入flag,则不需要同步)
由于在本例中锁定的是同一个对象,这意味着是的,因此线程2必须看到更新的标志值
虽然我在文档中没有看到任何关于保证PrintStream获得锁并且可以使用线程安全的内容,但是您依赖于这里的一个实现细节(尽管这个细节不太可能被破坏)。与System.out.println()相关的线程安全行为问题
通常在某人的代码中出现间歇性问题时出现,并且当他们添加“调试”时,println()
问题就会消失。println同步时发生的缓存一致性存储使错误“不可见”……但是
…它并没有让bug消失,只是让它发生的可能性大大降低,实际上,bug已经消失了。但是,删除println,bug就会回来
在您的代码示例中,您有两个带有布尔值的线程。当我阅读您的代码时,我解释所需的行为(线程安全)将是以下线程2中的行为:
Thread 2:
System.out.println();
while(flag){
System.out.println(); // PL2 - PrintLine2
}
该代码的目的是,只有当标志为true时,才会出现第二个println。
换句话说,在循环中,如果行是(将标志值添加到println):
然后,打印的结果应始终为“真”-决不为假
这似乎是这样,但这仅仅是因为这个代码是微不足道的…如果在打印之前有任何重大的“工作”发生,那么它变得更加明显……例如,考虑以下内容:
public class ThreadPrintln {
private static boolean flag = true;
private static class ThreadTwo implements Runnable {
@Override
public void run() {
long sum = 1L;
System.out.println("Thread2Start");
while (flag) {
// non-trivial code
for (int i = 0; i < 100000; i++) {
sum += i;
}
System.out.println(flag); // PL2 - PrintLine2
}
System.out.println("Sum is " + sum);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new ThreadTwo());
t.start();
System.out.println("Main About to wait");
Thread.sleep(1000);
System.out.println("Main about to set");
flag = false;
System.out.println("Main about to join");
t.join();
System.out.println("Main about to exit");
}
}
**请注意,它在倒数第二行打印了“false”**
使用println()不会使逻辑线程安全
随着“非平凡”代码变得越来越少,它减少了您出现不良行为的机会,但并不能消除它。删除“非平凡”代码并不能消除线程错误行为的机会,它只会使它变得非常小,以至于您在测试它时不太可能看到它……但是,在某一点上,它将<强>将 行为不端。线程安全与“锁定”不同。有很多算法,可见性是您所需要的。正如您的示例所示,另一个线程确实看到了预期的更新标志值(如果没有println,则获得无止境循环是完全可以接受的).给定的可见性保证是否足够取决于算法。
public class ThreadPrintln {
private static boolean flag = true;
private static class ThreadTwo implements Runnable {
@Override
public void run() {
long sum = 1L;
System.out.println("Thread2Start");
while (flag) {
// non-trivial code
for (int i = 0; i < 100000; i++) {
sum += i;
}
System.out.println(flag); // PL2 - PrintLine2
}
System.out.println("Sum is " + sum);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new ThreadTwo());
t.start();
System.out.println("Main About to wait");
Thread.sleep(1000);
System.out.println("Main about to set");
flag = false;
System.out.println("Main about to join");
t.join();
System.out.println("Main about to exit");
}
}
true
true
true
....
.... many true values
....
true
true
true
Main about to set
Main about to join
false
Sum is 38724612750001
Main about to exit