Java线程通信
螣蛇乘雾,终为土灰。
多个线程协同工作完成某个任务时就会涉及到线程间通信问题。如何使各个线程之间同时执行,顺序执行、交叉执行等。
创建两个线程a和b,两个线程内调用同一个打印 1-3 三个数字的方法。


1 package tjt; 2 3 import java.time.LocalDate; 4 5 public class Test { 6 7 /** 8 * 创建两个线程a和b,两个线程内调用同一个打印 1-3 三个数字的方法。 9 */10 private static void situationOne() {11 Thread a = new Thread(new Runnable() {12 @Override13 public void run() {14 doSomething("a");15 }16 });17 Thread b = new Thread(new Runnable() {18 @Override19 public void run() {20 doSomething("b");21 }22 });23 a.start();24 b.start();25 }26 27 /**28 * 依次打印 1, 2, 3 三个数字29 *30 * @param threadName31 */32 private static void doSomething(String threadName) {33 int i = 0;34 while (i++ < 3) {35 try {36 Thread.sleep(200);37 } catch (InterruptedException e) {38 e.printStackTrace();39 }40 System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + i);41 }42 }43 44 public static void main(String[] args) {45 situationOne();46 }47 }
多次运行发现a和b是同时打印的,无执行顺序可言。

创建两个线程a和b,要求b 在 a 全部打印完后再开始打印。使用 thread.join() 方法,在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行,即必须a执行完毕后才轮到b。


1 package tjt; 2 3 import java.time.LocalDate; 4 5 public class Test { 6 7 /** 8 * 创建两个线程a和b,要求b 在 a 全部打印完后再开始打印,使用 thread.join() 方法。 9 * 保证线程a执行完毕后才轮到b10 */11 private static void situationOne() {12 Thread a = new Thread(new Runnable() {13 @Override14 public void run() {15 doSomething("a");16 }17 });18 Thread b = new Thread(new Runnable() {19 @Override20 public void run() {21 try {22 System.out.println("线程 b 正在通过thread.join()等待线程 a 执行完毕后再润");23 // thread.join() 在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行,即必须a执行完毕后才轮到b24 a.join();25 } catch (InterruptedException e) {26 e.printStackTrace();27 }28 doSomething("b");29 }30 });31 a.start();32 b.start();33 }34 35 /**36 * 依次打印 1, 2, 3 三个数字37 *38 * @param threadName39 */40 private static void doSomething(String threadName) {41 int i = 0;42 while (i++ < 3) {43 try {44 Thread.sleep(200);45 } catch (InterruptedException e) {46 e.printStackTrace();47 }48 System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + i);49 }50 }51 52 public static void main(String[] args) {53 situationOne();54 }55 }
无论运行多少次,都是线程a先执行完毕再到线程b。

创建两个线程a和b,要求 a 在打印完 1 后,再让 b 打印 1、2、 3,接着再回到 a 继续打印 2、3。如此顺序交叉执行仅靠 Thread.join() 是无法满足需求的,需要更细粒度的锁来控制执行顺序,以及object.wait() 和 object.notify() 两个方法来实现。


1 package tjt; 2 3 import java.time.LocalDate; 4 5 public class TestAgain { 6 7 private static void situationTwo() { 8 // a 和 b 的共享对象锁 lock 9 Object lock = new Object();10 Thread a = new Thread(new Runnable() {11 @Override12 public void run() {13 // 同步锁 lock14 synchronized (lock) {15 // a 获得锁后执行16 doSomething("a", 1);17 try {18 // 调用 lock.wait() 方法,交出锁的控制权,进入 wait 状态,等待notify唤醒19 lock.wait();20 } catch (InterruptedException e) {21 e.printStackTrace();22 }23 doSomething("a", 2);24 doSomething("a", 3);25 }26 }27 });28 Thread b = new Thread(new Runnable() {29 @Override30 public void run() {31 // 同步锁 lock32 synchronized (lock) {33 // a 获得锁后执行34 doSomething("b", 1);35 doSomething("b", 2);36 doSomething("b", 3);37 // 调用 lock.notify() 方法,唤醒正在 wait 的线程 a38 lock.notify();39 }40 }41 });42 a.start();43 b.start();44 }45 46 /**47 * 打印48 *49 * @param threadName50 * @param num51 */52 private static void doSomething(String threadName, int num) {53 System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + num);54 }55 56 public static void main(String[] args) {57 situationTwo();58 }59 }
无论运行多少次,都是线程a先执行打印1,然后线程b执行打印1、2、3,最后线程a执行打印2、3。

CountDownLatch 计数器适用于一个线程去等待多个线程的情况。例如A B C 三个线程同时运行,各自独立运行完后通知线程 D 执行,就可以利用 CountdownLatch 来实现这类通信方式。
对比之前的join方法,thread.join()可以让一个线程等另一个线程运行完毕后再继续执行,其可以在 D 线程里依次 join A B C,但这样 A B C 必须依次执行,无法实现ABC三者能同步运行。


1 package tjt; 2 3 import java.time.LocalDate; 4 import java.util.concurrent.CountDownLatch; 5 6 public class TestCountDownLatch { 7 8 /** 9 * countDownLatch 适用于一个线程去等待多个线程的情况10 * 四个线程A、B、C、D,11 * 其中 D 要等到 A B C 全执行完毕后才执行,且 A B C 是同步运行的,即ABC无顺序执行12 */13 private static void situationThree() {14 // 初始计数值设置为3,即总共四个线程A、B、C、D15 CountDownLatch latch = new CountDownLatch(3);16 new Thread(new Runnable() {17 @Override18 public void run() {19 System.out.println(LocalDate.now() + "线程 D 等待线程A B C 执行完毕后才可执行");20 try {21 // await() 检查计数器值是否为 0,若不为 0 则保持等待状态22 latch.await();23 // 其他线程 的 countDown() 方法把计数值变成 0 时,等待线程里的 countDownLatch.await() 立即退出,继续执行下面的代码24 System.out.println(LocalDate.now() + "线程A B C 执行完毕,轮到线程D 执行了,当前latch:" + latch.getCount());25 } catch (InterruptedException e) {26 e.printStackTrace();27 }28 }29 }).start();30 31 // 循环执行线程 A B C32 for (char threadName = 'A'; threadName <= 'C'; threadName++) {33 String name = String.valueOf(threadName);34 new Thread(new Runnable() {35 @Override36 public void run() {37 System.out.println("线程 " + name + " is running");38 // countDown(),将倒计数器减 1, 计数器被减至 0 时立即触发D 的 await()39 try {40 Thread.sleep(200);41 } catch (InterruptedException e) {42 e.printStackTrace();43 }44 System.out.println("线程 " + name + " 执行完毕计数器");45 latch.countDown();46 }47 }).start();48 }49 }50 51 public static void main(String[] args) {52 situationThree();53 }54 55 }

实现线程间互相等待,可以利用 CyclicBarrier 栅栏。CountDownLatch 可以用来倒计数,但当计数完毕,只有一个线程的 await() 会得到响应,无法让多个线程同时触发。如要求线程 A B C 各自开始准备,直到三者都准备完毕再同时运行其就无法满足需求,而用CyclicBarrier则完全OK。

螣蛇乘雾
终为土灰