线程之间的通信

通过几个面试题来了解线程之间的通信。
问题清单:

  • 如何让两个线程依次执行?
  • 如何让两个线程按照指定方式有序交叉运行?
  • 四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的。
  • 三个线程各自准备,等到三个线程都准备好后,再一起执行某个任务。
  • 子线程完成某件任务后,把得到的结果回传给主线程。

两个线程依次执行?
假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字:
这个可说的不多,就是在B线程中加入A.join();
代码如下:
线程之间的通信

如何让两个线程按照指定方式有序交叉运行呢?
假如, A 在打印完 1 后,再让 B 打印 1, 2, 3,最后再回到 A 继续打印 2, 3。可以利用 object.wait() 和 object.notify() 两个方法来实现。
思路:两个线程使用同一把锁,A线程打印完1后调用wait()方法释放锁(此时的B没有锁,并没执行打印的动作),A释放了锁之后B获得了锁,开始执行打印1 2 3的动作,执行完毕的B线程调用notify()唤醒在wait的A线程,A继续打印2 3。
代码如下:
线程之间的通信

四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的
要求是同时运行,所以不能使用join(),因为ABC会顺序执行。换言之,就是ABC执行完成后通知D,然后D再执行他的任务。这种情况,我们可以利用 CountdownLatch 来实现这类通信方式CountDownLatch 适用于一个线程去等待多个线程的情况。
先解释一下CountdownLatch,CountdownLatch是一个计数器,设置一下数量(本题设置成3就好),计数器为0时线程开始执行任务。
思路:在D线程中调用countDownLatch.await()进入等待状态,直到计数器为0。在ABC线程中,执行完各自的任务后调用countDownLatchcountDown()方法,这样计数器会 -1。一旦countDownLatch计数器变成0,countDownLatch.await()立即退出,继续执行代码。
代码如下:
线程之间的通信

三个线程各自准备,等到三个线程都准备好后,再一起执行某个任务
线程 A B C 各自开始准备,直到三者都准备完毕,然后再同时运行。实现一种 线程之间互相等待 的效果。
相互等待的效果,可以使用CyclicBarrier(栅栏) 数据结构。
思路: 创建一个公共的CyclicBarrier,然后设置同时等待的线程数(ABC三个线程,设置成3即可)。然后在ABC三个线程中,分别准备完毕后调用CyclicBarrier.await()方法,开始等待其他线程。ABC三个线程都调用了await()方法后,再分别执行后面的代码。
代码如下:
线程之间的通信

子线程完成某件任务后,把得到的结果回传给主线程。
创建一个线程,我们一般会把 Runnable 对象传给 Thread 去执行,调用start之后执行的是run()方法,而run()方法是没有返回值的,也就没法把结果返回。
如果想接收到返回值,可以使用类似于Runnable的接口:Callable
Callable接口的定义如下:
线程之间的通信
最明显的区别就是返回结果是范型 V 。
返回的结果要使用另一个类来接受:FutureTask。它是配合Callable使用的。值得注意的一点,FutureTask它获取结果的 get 方法会阻塞主线程。
举例:子线程去计算从 1 加到 100,并把算出的结果返回到主线程。
代码如下:
线程之间的通信
执行结果如下:
线程之间的通信
从执行结果可以看出:
主线程调用 futureTask.get() 方法时阻塞主线程;
然后 Callable 内部开始执行,并返回运算结果;
此时 futureTask.get() 得到结果,主线程恢复运行。

这里我们可以学到:
通过 FutureTask 和 Callable 可以直接在主线程获得子线程的运算结果,只不过需要阻塞主线程。
如果不希望阻塞主线程,可以考虑利用 ExecutorService,把 FutureTask 放到线程池去管理执行。