在线程安全的System.out上,Java多线程输出似乎不明确



这是我的代码:

class Question extends Thread    {
static String info;
public Question(String info )    {
this.info = info;
}
private void inProtected () {
synchronized ( info )       {
System.out.println("--> " + info +  " Hi");
System.out.println("<-- " + info +  " Hi");
}
}
public void run () {
inProtected();
}
public static void main (String args []) {
new Question("a").start();
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Question("ab").start();
}
}

这段代码有时会产生这样的输出:

--> ab Hi
--> a Hi //Wait a second, what? How can "a" print after "ab"?
<-- ab Hi
<-- ab Hi

我看过System.out的代码和StackOverflow上的几个答案,我知道System.out是线程安全的。那么,如果info是静态的,并且主线程按顺序创建两个线程(带有"a"one_answers"ab"(,那么这个执行顺序怎么可能呢?

编辑:我非常清楚Protected中的方法是不同步的,因为值是由不同的线程更改的。我的问题是,"a"怎么可能在"ab"之后打印,因为主线程按顺序创建线程,从而修改静态变量信息?

这就是示例中发生的情况

主线程:

  • 创建第一个线程:info="a"(1(
  • 启动线程1(2(
  • 睡眠1毫秒(3(
  • 创建第二个线程:info="ab"(4(
  • 启动线程2(5(

线程1:

  • 打印第一行:"a"(7(->即使主线程已经将信息更改为"ab",线程1也看不到它
  • 打印第二行:"ab"(8(

线程2:

  • 打印第一行:"ab"(6(
  • 打印第二行:"ab"(9(

观察

  1. 调用sleep不会提供Thread1将在Thread2之前开始执行的任何合约。由操作系统的线程调度程序决定哪个线程将首先开始执行,在这种情况下,它们之间没有确定的顺序
  2. Thread1可以从info中读取一个过时的值,因为该字段的内存操作上不存在之前发生的关系

挥发性

为了避免看到info字段的陈旧值,可以将其声明为volatile。这将确保在此字段之前发生写入;任何后续阅读。

有关发生在关系和内存可见性之前的更多信息,请点击此处:内存一致性属性

synchronized块中的System.out.print不受同一个锁的保护("a""ab"是不同的String对象(,因此inProtected实际上不受保护。

此外,线程的执行顺序由操作系统决定。不能保证new Question("a")将在new Question("ab")之前启动。

更新:

奇怪的是,如果我删除睡眠是主要的方法。

try { sleep(1) }的存在使第一线程有可能在执行new Question("ab")之前启动并读取/缓存字符串"a",其中值变为"ab"

顺便说一下,你可以替换:

try { sleep(1); } catch (Exception e) { }

比如:

for (int i = 0; i < 1_000_000; i++);

并且您将获得与CCD_ 17类似的行为。

最新更新