Java 里如何实现线程间通信?

正常情况下,每个子线程完成各自的任务就可以结束了。不过有的时候,我们希望多个线程协同工作来完成某个任务,这时就涉及到了线程间通信了。

本文涉及到的知识点:thread.join(), object.wait(), object.notify(), CountdownLatch, CyclicBarrier, FutureTask, Callable 等。

本文涉及代码:ForArticle.java

下面我从几个例子作为切入点来讲解下 Java 里有哪些方法来实现线程间通信。

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

如何让两个线程依次执行?

假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们来看下代码:

  • private static void demo1() {
  • Thread A = new Thread(new Runnable() {
  • @Override
  • public void run() {
  • printNumber("A");
  • }
  • });
  • Thread B = new Thread(new Runnable() {
  • @Override
  • public void run() {
  • printNumber("B");
  • }
  • });
  • A.start();
  • B.start();
  • }

其中的 printNumber(String) 实现如下,用来依次打印 1, 2, 3 三个数字:

  • private static void printNumber(String threadName) {
  • int i=0;
  • while (i++ < 3) {
  • try {
  • Thread.sleep(100);
  • } catch (InterruptedException e) {
  • e.printStackTrace();
  • }
  • System.out.println(threadName + " print: " + i);
  • }
  • }

这时我们得到的结果是:

B print: 1
A print: 1
B print: 2
A print: 2
B print: 3
A print: 3

可以看到 A 和 B 是同时打印的。

那么,如果我们希望 B 在 A 全部打印完后再开始打印呢?我们可以利用 thread.join() 方法,代码如下:

  • private static void demo2() {
  • Thread A = new Thread(new Runnable() {
  • @Override
  • public void run() {
  • printNumber("A");
  • }
  • });
  • Thread B = new Thread(new Runnable() {
  • @Override
  • public void run() {
  • System.out.println("B 开始等待 A");
  • try {
  • A.join();
  • } catch (InterruptedException e) {
  • e.printStackTrace();
  • }
  • printNumber("B");
  • }
  • });
  • B.start();
  • A.start();
  • }

得到的结果如下:

B 开始等待 A
A print: 1
A print: 2
A print: 3

B print: 1
B print: 2
B print: 3

所以我们能看到 A.join() 方法会让 B 一直等待直到 A 运行完毕。

那如何让个线程按照指定方式有序交叉运行呢?

还是上面那个例子,我现在希望 A 在打印完 1 后,再让 B 打印 1, 2, 3,最后再回到 A 继续打印 2, 3。这种需求下,显然 Thread.join() 已经不能满足了。我们需要更细粒度的锁来控制执行顺序。

这里,我们可以利用 object.wait()object.notify() 两个方法来实现。代码如下:

  • /**
  • * A 1, B 1, B 2, B 3, A 2, A 3
  • */
  • private static void demo3() {
  • Object lock = new Object();
  • Thread A = new Thread(new Runnable() {
  • @Override
  • public void run() {
  • synchronized (lock) {
  • System.out.println("A 1");
  • try {
  • lock.wait();
  • } catch (InterruptedException e) {
  • e.printStackTrace();
  • }
  • System.out.println("A 2");
  • System.out.println("A 3");
  • }
  • }
  • });
  • Thread B = new Thread(new Runnable() {
  • @Override
  • public void run() {
  • synchronized (lock) {
  • System.out.println("B 1");
  • System.out.println("B 2");
  • System.out.println("B 3");
  • lock.notify();
  • }
  • }
  • });
  • A.start();
  • B.start();
  • }

打印结果如下:

A 1
A waiting...

B 1
B 2
B 3
A 2
A 3

正是我们要的结果。

那么,这个过程发生了什么呢?

  1. 首先创建一个 A 和 B 共享的对象锁 lock = new Object();
  2. 当 A 得到锁后,先打印 1,然后调用 lock.wait() 方法,交出锁的控制权,进入 wait 状态;
  3. 对 B 而言,由于 A 最开始得到了锁,导致 B 无法执行;直到 A 调用 lock.wait() 释放控制权后, B 才得到了锁;
  4. B 在得到锁后打印 1, 2, 3;然后调用 lock.notify() 方法,唤醒正在 wait 的 A;
  5. A 被唤醒后,继续打印剩下的 2,3。

为了更好理解,我在上面的代码里加上 log 方便读者查看。

  • private static void demo3() {
  • Object lock = new Object();
  • Thread A = new Thread(new Runnable() {
  • @Override
  • public void run() {
  • System.out.println("INFO: A 等待锁");
  • synchronized (lock) {
  • System.out.println("INFO: A 得到了锁 lock");
  • System.out.println("A 1");
  • try {
  • System.out.println("INFO: A 准备进入等待状态,放弃锁 lock 的控制权");
  • lock.wait();
  • } catch (InterruptedException e) {
  • e.printStackTrace();
  • }
  • System.out.println("INFO: 有人唤醒了 A, A 重新获得锁 lock");
  • System.out.println("A 2");
  • System.out.println("A 3");
  • }
  • }
  • });
  • Thread B = new Thread(new Runnable() {
  • @Override
  • public void run() {
  • System.out.println("INFO: B 等待锁");
  • synchronized (lock) {
  • System.out.println("INFO: B 得到了锁 lock");
  • System.out.println("B 1");
  • System.out.println("B 2");
  • System.out.println("B 3");
  • System.out.println("INFO: B 打印完毕,调用 notify 方法");
  • lock.notify();
  • }
  • }
  • });
  • A.start();
  • B.start();
  • }

打印结果如下:

top Created with Sketch.