有些知识点还是要一个一个来,一文秒懂 系列还是来的有点肤浅。这几天开始尝试深入 API 层写一些东西,看看效果如何?
本章节我尝试详细的介绍下 Thread.join()
方法。然后过几天回来看看这篇文章的质量如何?
Thread.join()
方法有许多重载,可用于各种场景,本文也会针对一些较常见的场景写一些使用 Thread.join()
的示例。
Thread.join() 方法
Thread.join() 方法和 wait()
方法和 notify()
等方法一起,用于实现 Java 线程间的同步机制。
join()
方法在 Thread 类中定义
public final void join() throws InterruptedException
按照官方的解释,join()
方法用于等待线程的终止。
当我们在线程上调用 join() 方法时,调用线程会进入等待状态,且一直保持等待状态,直到引用的线程终止。
为了让大家理解上面这句话,请你尝试运行下面这个测试示例
class SampleThread extends Thread { public int processingCount = 0; SampleThread(int processingCount) { this.processingCount = processingCount; LOGGER.info("Thread Created"); } @Override public void run() { LOGGER.info("Thread " + this.getName() + " started"); while (processingCount > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { LOGGER.info("Thread " + this.getName() + " interrupted"); } processingCount--; } LOGGER.info("Thread " + this.getName() + " exiting"); } } @Test public void givenStartedThread_whenJoinCalled_waitsTillCompletion() throws InterruptedException { Thread t2 = new SampleThread(1); t2.start(); LOGGER.info("Invoking join"); t2.join(); LOGGER.info("Returned from join"); assertFalse(t2.isAlive()); }
执行上面这段代码,我们期望的结果应该为
INFO: Thread Created INFO: Invoking join INFO: Thread Thread-1 started INFO: Thread Thread-1 exiting INFO: Returned from join
如果引用的线程被中断,join() 方法会在返回的同时抛出 InterruptedException 异常。
如果引用的线程已经终止或尚未启动,那么调用 join() 方法会立即返回。
Thread t1 = new SampleThread(0); t1.join(); //立即返回
Thread.join() 与超时机制
如果引用的线程被阻塞或者处理时间太长,join() 方法将继续等待。
这可能会是一个隐患,因为可能导致调用线程变得无响应。
为了解决这个隐患,join() 方法有一个重载版本,接受一个超时时间,在到达超时时间后如果引用线程仍未处理完毕,那么 join() 方法立即中断引用线程的执行并抛出一个 InterruptedException 异常。
携带超时时间参数的 join() 方法有两个重载版本,定义分别如下
-
public final void join(long millis) throws InterruptedException
这个重载版本指示调用线程最多等待 millis 毫秒。超时为
0
意味着永远等待。 -
public final void join(long millis,int nanos) throws InterruptedException
这个重载版本指示调用线程最多等待 millis 毫秒加上 nanos 纳秒时间。超时为
0
意味着永远等待。
使用这两个 join() 重载方法,我们可以将上面示例中的 givenStartedThread_whenJoinCalled_waitsTillCompletion
方法改成
@Test public void givenStartedThread_whenTimedJoinCalled_waitsUntilTimedout() throws InterruptedException { Thread t3 = new SampleThread(10); t3.start(); t3.join(1000); assertTrue(t3.isAlive()); }
上面这段代码,调用线程等待大约 1 秒钟,以便线程 t3 完成。如果线程 t3 在此时间段内没有完成,join() 方法将控制权返回给调用方法。
注意: 这两个超时版本的 join() 重载方法实际上等待的超时间隔取决于操作系统。我们不能假定 join() 将完全等待指定的时间。
Thread.join() 方法和同步执行
除了等待终止之外,调用 join()
方法还具有同步效果。 join()
创建一个 happens-before 关系。
官方文档是这么说明的
All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
翻译成中文:
「 线程中的所有操作都发生在任何其他线程从该线程上的 join() 成功返回之前 」
我蹩脚的翻译,怎么翻译都感觉不对口。
这段描述文字的大概意思是:当线程 t1 调用 t2.join() 时,t2 结束之前完成的所有更改,在 t2 结束时 t1 都可以获取到。
简单的说,就是如果 t1 调用了 t2.join(),那么 t1 就会暂停执行,直到 t2 执行完毕,才继续往下执行。
但是,如果我们不调用 join() 或使用其他同步机制,我们就无法保证其他线程的更改对当前线程可见,即使其他线程已完成。
因此,即使 join() 方法调用处于终止状态的线程会立即返回,即便如吃,我们仍然需要在某些情况下调用它。
比如下面的代码,就是一个不正确同步示例
SampleThread t4 = new SampleThread(10); t4.start(); // 即使 t4 结束也不能保证停止 do { } while (t4.processingCount > 0);
为了确保上面的代码正确的正确同步,我们可以在循环中添加 t4.join() 的超时重载版本或使用其他一些同步机制。