在下面的代码中,答案始终是开始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);
}
}
答案 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()
时订单不同,这样做是因为您只有一个帖子。