2013-09-30 84 views
2

试图围绕此代码包裹我的头。当我运行这个 - 输出将是Roger。是不是味精是一个静态变量,并在课堂上因此应该打印摩尔线程和静态变量

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

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); 
    } 
} 
+1

它看起来像一个简单的竞争条件。我想知道,在main()中,如果你睡一秒钟,它会输出“Moore”而不是“Roger”吗? – CmdrMoozy

+0

是的 - 就是这样..加入一个Thread.sleep更长的时间没有打印相关的输出!感谢你的回答!请添加你的答案,以便我可以“回答”它..或者我可以回答papmplhet - 两个都是正确的... – user2796381

回答

4

试图围绕此代码包裹我的头。当我运行这个时 - 输出将是罗杰。是不是msg是一个静态变量,因此在课堂上应该打印Moore?

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

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

当一个类被初始化时,static代码在首先访问类的线程中执行 - 在这种情况下是主线程。所有其他线程必须等待此初始化完成才能访问该类。这意味着后台线程实际上停止并且等待的类初始化完成,然后它可以执行msg += "Moore";。然后,在main打印之前,查看信息是否分配给"Roger"并且后台线程可以附加到。即使msg字段为volatile,比赛依然存在。您可以从JLS section 12.4.2 on Detailed Initialization Procedure中了解过程的复杂性。

所以发生大约是:

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

一般来说,在static这样的方法中分叉后台线程是极其令人不悦的。显然不建议将sleep置于static块中。如果主线程首先aquires锁

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

+0

+1:有趣。 JSL中有关于发生了什么的章节? –

+0

找到了@MartijnCourteaux。有关详细初始化过程的第12.4.2节。 http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.2 – Gray

+0

我正在读一两分钟的那款,但我认为这更多的是WHO(=哪个线程)正在初始化该类,而不是真正阻止对类成员的访问。但是,我会重读。 –

2

这是一种竞争条件。无法保证Runnable何时执行。

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

+1

这似乎不是我的竞争条件。睡一秒钟应该让另一条线足够的时间完成。检查这个例子:http://ideone.com/DVMZ0f –

+0

@MartijnCourteaux,OP已经证实,延迟主线程改变了输出。 – pamphlet

+0

对不起,但我是downvoting。此行为是由于Java保护其他线程访问未初始化类的变量而引起的。 –

2

直到你的类中的所有静态初始值设定项完成后,才会调用main方法。所以它会一直等到静态入口完成。即使它有睡眠。

另外静态初始化是线程安全的,所以你分叉的线程不能访问变量,直到静态init块完成。

0

与其等待一点点,并希望其他线程运行,你可以用一些同步机制保障它。它将不会继续,直到调用notify(实际上,它在包含通知的同步块完成时继续)。如果新线程首先获得锁,那么主线程将不得不在其同步块的开始处等待。一旦进入,完成将是真实的。它不会等待,直接通过。

+0

由于几个原因,这不起作用。如果它在多个线程中更新,'done'应该是'volatile'。但真正的问题是,你正试图在引用正在改变的对象上同步。这从来没有工作。 – Gray

+0

@Gray''fixed'' – Cruncher

+0

如果可能的话,锁定对象应该始终是'final'Cruncher。否则这看起来不错。 – Gray