3

试图绕过这段代码。当我运行它时 - 输出将是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); 
    }
}
4

4 回答 4

5

试图绕过这段代码。当我运行它时 - 输出将是 Roger。msg 不是静态变量,因此应该在类级别打印 Moore 吗?

正如其他人指出的那样,这是一个竞争条件,但它比这个简单的答案更复杂。

编辑:我也允许睡眠允许子线程运行它的过程。它还打印打印...仍然没有变化

当一个类被初始化时,static代码在首先访问该类的线程中执行——在本例中是主线程。所有其他线程必须等待此初始化完成才能访问该类。这意味着后台线程实际上会停止并等待类初始化完成,然后才能执行msg += "Moore";。然后是一场比赛,看是否分配了 msg "Roger",后台线程是否可以在打印之前附加到它。 main即使有msgvolatile,比赛仍然存在。您可以从JLS 第 12.4.2 节的详细初始化过程中了解该过程的复杂性。

所以发生的事情大约是:

  1. 主线程初始化Test2类。
  2. 首先msg初始化,因为它在static块之前。
  3. static执行分叉后台线程的第一个块。
  4. 第二个static块被执行,它sleep()阻塞了初始化线程。
  5. 后台线程开始运行(可能在上一步之前)。它会进行更新msg,但由于主线程正在睡眠并且尚未完成类初始化,因此类被锁定。后台线程必须等待。
  6. 主线程唤醒并完成初始化。
  7. 这释放了允许后台线程继续的类上的块。
  8. 与上一步main同时调用,在打印输出之前查看是否msg可以更新是一个竞争条件。

一般来说,在static这样的方法中分叉后台线程是非常不受欢迎的。显然也不建议将 asleep放入块中。static

于 2013-09-30T19:03:14.427 回答
2

这是一个竞赛条件。无法保证遗嘱何时Runnable执行。

编辑:这个答案回应了原始发布的问题,其中静态初始化程序中没有延迟。这导致了读取静态成员的主线程和更新它的衍生线程之间的简单竞争条件。

于 2013-09-30T18:50:03.007 回答
2

在您的类中的所有静态初始化程序都完成之前,不会调用 main 方法。所以它会一直等到静态初始化完成。哪怕里面有睡意。

另外静态初始化是线程安全的,因此在静态初始化块完成之前,您的分叉线程无法访问该变量。

于 2013-09-30T18:58:26.527 回答
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(实际上,它会在包含通知的同步块完成时继续)。如果新线程首先获得锁,那么主线程将不得不在其同步块的开始处等待。一旦它进入,done 将是真实的。它不会等待,直接通过。

于 2013-09-30T19:04:03.730 回答