下面是我的示例代码,当我的a.start()
调用时,它应该创建一个线程并立即打印"Run"。但是为什么在打印了"begin"20次之后又调用了呢?
线程a如何决定它不需要立即调用run()
public class JoinTest implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(new JoinTest());
a.start();
for (int i = 0; i < 20; i++) {
System.out.print("Begin");
}
Thread.sleep(1000);
a.join();
System.out.print("nEnd");
}
public void run() {
System.out.print("nRun");
}
}
输出:BeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBeginBegin
Run
End
我对线程的行为有点困惑。
在我看来,"run"
应该在"begin"
之前打印,因为它在join()
方法被调用之前被打印,并且在称为线程"a"的连接方法的时候必须完成它的执行,调用join在那时必须是无用的。
在线程上调用start()
不一定会立即触发其run()
方法的执行。你的线程被标记为已启动,但主线程在for
循环中继续执行。然后一旦主线程到达sleep()
语句,JVM就切换到您的线程。
启动线程,然后立即执行一些打印操作,然后休眠。看看你的代码,我实际上期望在Run
之前看到Begin
,因为线程正在后台启动,同时与主线程一起工作。此外,print
方法是同步的,因此您可以在循环中反复获取该锁,从而使第二个线程插入的机会更少。
我已经尝试了你的代码与Thread.sleep
消除,有和没有join
调用。在这两种情况下,行为是相同的:Run
在大多数情况下出现在最后,偶尔会在Begin
单词之间交错出现。一切都完全符合同步块并发的简单模型。
在你的代码中有一个细微的变化,无论你是否调用join
方法,它都可靠地在其他一切之前打印Run
。唯一改变的是sleep
调用的位置。
public class JoinTest implements Runnable
{
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(new JoinTest());
a.start();
Thread.sleep(1000);
for (int i = 0; i < 20; i++) {
System.out.print("Begin");
}
a.join();
System.out.print("nEnd");
}
public void run() {
System.out.print("nRun");
}
}
start()
方法调用线程的run()
方法,尽管这需要一点时间,这可能足以完成一些或所有的'Begin'循环。当我在我的机器上运行这个命令时,' run '输出出现在第一个和第二个'Begin'之间。尝试使用时间戳输出,这样您就可以看到您的机器执行每个命令所花费的时间:
public class JoinTest implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread a = new Thread(new JoinTest());
a.start();
for (int i = 0; i < 20; i++) {
System.out.println("Begin " + System.nanoTime());
}
Thread.sleep(1000);
a.join();
System.out.println("End " + System.nanoTime());
}
public void run() {
System.out.println("Run " + System.nanoTime());
}
}
在那个点调用a.join()
确保你总是在'End'之前看到'Run'输出,因为join()等待线程完成。
我用join()尝试了许多场景,"运行"总是在"开始"之后打印,但是当我删除连接语句"运行"时总是在"开始"之前打印。为什么?
因为这是允许的。这就是为什么。只要调用.start(),就有两个线程。在程序调用.join()之类的同步方法之前,完全取决于JVM实现和操作系统来决定哪个线程何时运行。
主线程的a.join()
调用强制主线程等待,直到a
线程完成它的run()
方法。
在没有a.join()
调用的情况下,Java语言规范允许在a.start()
返回主线程之前完成a
线程的工作,并且允许主线程在a
线程开始运行之前到达main()
函数的结束,并且允许介于两者之间的任何事情发生。
这完全取决于JVM和操作系统。
如果你想控制事情发生的顺序,那么你必须使用synchronized
块,以及同步对象和方法(例如,.join())。
但小心!你越是强迫事情按照特定的顺序发生,你的程序从使用线程中得到的好处就越少。在设计程序时,最好让线程在大多数情况下可以相互独立地工作。