这是我的代码:
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(
观察
- 调用
sleep
不会提供Thread1将在Thread2之前开始执行的任何合约。由操作系统的线程调度程序决定哪个线程将首先开始执行,在这种情况下,它们之间没有确定的顺序 - 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类似的行为。