线程如何知道前面有一个连接方法?



下面是我的示例代码,当我的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())。

但小心!你越是强迫事情按照特定的顺序发生,你的程序从使用线程中得到的好处就越少。在设计程序时,最好让线程在大多数情况下可以相互独立地工作。

最新更新