关于Java Thread的问题,输出一致

时间:2011-06-09 05:03:14

标签: java multithreading

在下面的代码中,答案始终是开始0 1 2 3完成。我只是想知道它是如何可能的。

如果有人可以帮助提高输出的一致性,那就很好了

public class TestOne extends Thread {

/**
 * @param args
 */
public static void main(String[] args)throws Exception {
    // TODO Auto-generated method stub

    Thread t = new Thread(new TestOne());
    t.start();
    System.out.println("started");
    t.join();
    System.out.println("Complete");




}

public void run(){
    for(int i=0;i<4;i++){
        System.out.println(i);
    }
}

6 个答案:

答案 0 :(得分:8)

很可能你得到了相同的结果,因为在大多数情况下,主线程开始一个新线程然后,在新线程有机会打印任何东西之前,主线程打印其started消息。然后主线程中的join强制它等待另一个线程完成,然后打印Complete

你这里有竞争条件。在你开始第二个线程的那一刻,关于线路将被输出的顺序是不确定的(除complete线之外,由wait调用确定为public class TestOne extends Thread { public static void main (String[] args) throws Exception { Thread t = new Thread (new TestOne()); t.start(); Thread.sleep (1000); // <- Added this. System.out.println ("Started"); t.join(); System.out.println ("Complete"); } public void run() { for (int i = 0; i < 4; i++) { System.out.println (i); } } } ,如前所述。 / p>

但竞争条件并非保证您将在多次运行中获得不同的结果,只有这样才有可能。当然,仍然不应该依赖于这种行为。

例如,以下代码:

0
1
2
3
Started
Complete

将输出:

sleep

尽管如此,即使这样,订单仍然无法保证,因为线程“热身”可能需要一秒多的时间 - {{1}}很少能成为竞争条件的良好解决方案。我刚刚在这里用它来说明目的。

答案 1 :(得分:3)

当你说Thread t = new Thread();没有什么特别的事情发生,因为你本身并没有创建"Thread"。它只是一个具有“特征”的对象,成为执行线程。要使对象成为"Thread",您必须调用t.start();,这就是魔法所在。

当您说t.start()时,JVM会为新创建的线程创建一个新堆栈。它将该堆栈与相关的线程对象相关联。然后使其可用于安排。基本上这意味着它将线程排入JVM调度程序中,并在下一个时间片中也可以执行。 JVM实际上做的远不止这些,我的答案只是过于简化了你的例子。

同时,所有这些线程+堆栈创建,你的主线程有机会转移到下一条指令,在你的情况下是System.out.println("started");

从理论上讲,你所说的是真的,"Started"可以来自0, 1, 2, 3之间的任何地方。但实际上,由于t.start()是一种“昂贵”的方法,因此需要一些时间才能完成,在此期间主线程通常有机会执行下一条指令。

如果您想了解t.start();查看Java源代码的详细信息。

答案 2 :(得分:2)

显然,您看到了一致的结果(以及此特定结果),因为在您的计算机上,在主线程中run之后始终发生对子线程println方法的调用。

为什么一致?

好吧,原因很简单,因为您平台的本机线程库的行为一致!

典型的现代Java虚拟机实现使用主机操作系统的本机线程支持来实现Java线程,并执行Java线程调度。在您的机器上,本机线程实现似乎始终允许当前线程立即从Thread.start()调用返回并继续执行。

然而,并不能保证这一切都会发生。例如,如果机器负载很重并且主线程刚刚耗尽其当前时间片,则可以在start调用期间或之后立即进行调度,从而允许新线程首先运行。

此外,在另一个平台上,正常的调度程序行为可能不同。调度程序可以始终如一地导致当前线程取消调度并让新的线程先行。或者它可能“随机”发生。

JVM和Java库规范故意不指定哪个线程“先行”以允许线程实现的差异......以及由于硬件,系统负载等的差异而导致的变化。


底线 - 您看到的表观一致性是一个“工件”,您不应该依赖它,特别是如果您希望您的应用程序在各种JVM上工作。

答案 3 :(得分:0)

t.join()表示“阻止线程t完成”,这解释了“已完成”是最后一次。

编辑:在回答问题时“重新开始”...

对Thread.start()的调用意味着“请安排此线程运行”,并且当java感觉像启动它时它将启动。在t.start()println()之间发生这种情况的可能性是平台依赖,但在我使用的系统上很小( thx到@ Stephen C获取平台信息 )。

此代码输出0,已启动,1,2,3,已完成:

    Thread t = new Thread(new TestOne());
    t.start();
    try
    {
        Thread.sleep(100); // Added sleep between t.start() and println()
    }
    catch (InterruptedException e)
    {
        e.printStackTrace();
    }
    System.out.println("Started");
    t.join();
    System.out.println("Complete");

答案 4 :(得分:0)

简单地说,启动线程相对于主线程的调度是依赖于JVM实现的。但话虽如此,大多数实现,如果不是全部,将使开始线程运行到其时间片的完成,直到它阻塞某些东西,或直到它被更高优先级的线程抢占(如果正在使用抢占式调度,也依赖于JVM实现。)

Java规范并没有详细说明哪些是非常具体的线程,故意授予JVM编写者最大的实现范围。

答案 5 :(得分:0)

当你开始一个线程时,它需要时间(没有任何东西免费或立即发生) 已经运行的线程几乎总是比刚刚开始的线程更快打印出来。

如果您真的希望每次使用run()代替start()时订单不同,这样做是因为您只有一个帖子。