试图绕过这段代码。当我运行时 - 输出将是 Roger 。 msg是静态变量而不是类级,因此应该打印 Moore ?
编辑:我允许睡眠也允许子线程运行。它还打印打印.. 。仍然没有变化
public class Test2 {
private static String msg = "Roger";
static {
new Thread(new Runnable() {
public void run() {
System.out.println("printing..");
msg += "Moore";
}
}).start();
}
static {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
public static void main(String argv[]) {
System.out.println(msg);
}
}
答案 0 :(得分:4)
试图绕过这段代码。当我运行它 - 输出将是罗杰。 msg是静态变量而不是类级别因此应该打印Moore?
正如其他人所指出的那样,这是一种竞争条件,但它比这个简单的答案更复杂。
编辑:我允许睡眠也允许子线程运行。它还打印印刷......仍然没有变化
初始化类时,static
代码首先在访问类的线程中执行 - 在本例中为主线程。所有其他线程必须等待此初始化完成才能访问该类。这意味着后台线程实际上会停止并且等待以使类初始化完成,然后才能执行msg += "Moore";
。然后,在 "Roger"
打印它之前,查看msg是否已分配给main
并且后台线程可以附加到是一场竞赛。即使msg
字段为volatile
,竞赛仍然存在。您可以从JLS section 12.4.2 on Detailed Initialization Procedure了解过程的复杂性。
所以发生的事情大致是:
Test2
类。msg
首先被初始化,因为它来自static
阻止。static
块,它会分配后台线程。static
阻止,sleep()
阻止初始化线程。msg
,但由于主线程处于休眠状态并且尚未完成类初始化,因此该类已被锁定。后台线程必须等待。main
,看看msg
在打印之前是否可以更新是一种竞争条件。通常,在这样的static
方法中分支后台线程是非常不满意的。显然也不建议将sleep
放在static
块中。
答案 1 :(得分:2)
这是竞争条件。 Runnable
执行时无法保证。
编辑:此答案响应原始发布的问题,其中静态初始化程序中不存在延迟。这导致读取静态成员的主线程与更新它的生成线程之间存在简单的竞争条件。
答案 2 :(得分:2)
在完成类中的所有静态初始化程序之前,不会调用main方法。所以它总是会等到静态内容完成。即使有睡眠。
另外静态初始化是线程安全的,所以你的分叉线程无法访问变量,直到完成静态init块。
答案 3 :(得分:0)
而不是等待一点,并希望另一个线程运行,你可以通过一些同步保证它:
public class Test {
private static String msg = "Roger";
private static volatile boolean done = false;
private static final Object lock = new Object();
static {
new Thread(new Runnable() {
public void run() {
synchronized(lock)
{
lock.notify();
System.out.println("printing..");
msg += "Moore";
done=true;
}
}
}).start();
}
public static void main(String argv[]) {
synchronized(lock)
{
while(!done)
{
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
System.out.println(msg);
}
}
如果主线程首先获取锁定,那么它将msg.wait
。在调用notify
之前它不会继续(实际上,当包含notify的synchronized块完成时它会继续)。如果新线程首先获取锁,则主线程必须在其同步块的开始处等待。一旦进入,完成将是真的。它不会等待,直接通过。