Java线程通信

      螣蛇乘雾,终为土灰。

多个线程协同工作完成某个任务时就会涉及到线程间通信问题。如何使各个线程之间同时执行,顺序执行、交叉执行等。

一、线程同时执行

创建两个线程a和b,两个线程内调用同一个打印 1-3 三个数字的方法。

Java线程通信-LMLPHPJava线程通信-LMLPHP
 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             @Override
13             public void run() {
14                 doSomething("a");
15             }
16         });
17         Thread b = new Thread(new Runnable() {
18             @Override
19             public void run() {
20                 doSomething("b");
21             }
22         });
23         a.start();
24         b.start();
25     }
26
27     /**
28      * 依次打印 1, 2, 3 三个数字
29      *
30      * @param threadName
31      */
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 }
View Code

多次运行发现a和b是同时打印的,无执行顺序可言。

Java线程通信-LMLPHP

二、线程顺序执行

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

Java线程通信-LMLPHPJava线程通信-LMLPHP
 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执行完毕后才轮到b
10      */
11     private static void situationOne() {
12         Thread a = new Thread(new Runnable() {
13             @Override
14             public void run() {
15                 doSomething("a");
16             }
17         });
18         Thread b = new Thread(new Runnable() {
19             @Override
20             public void run() {
21                 try {
22                     System.out.println("线程 b 正在通过thread.join()等待线程 a 执行完毕后再润");
23                     // thread.join() 在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行,即必须a执行完毕后才轮到b
24                     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 threadName
39      */
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 }
View Code

无论运行多少次,都是线程a先执行完毕再到线程b。

Java线程通信-LMLPHP

三、线程顺序交叉执行

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

Java线程通信-LMLPHPJava线程通信-LMLPHP
 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             @Override
12             public void run() {
13                 // 同步锁 lock
14                 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             @Override
30             public void run() {
31                 // 同步锁 lock
32                 synchronized (lock) {
33                     // a 获得锁后执行
34                     doSomething("b", 1);
35                     doSomething("b", 2);
36                     doSomething("b", 3);
37                     // 调用 lock.notify() 方法,唤醒正在 wait 的线程 a
38                     lock.notify();
39                 }
40             }
41         });
42         a.start();
43         b.start();
44     }
45
46     /**
47      * 打印
48      *
49      * @param threadName
50      * @param num
51      */
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 }
View Code

无论运行多少次,都是线程a先执行打印1,然后线程b执行打印1、2、3,最后线程a执行打印2、3。

Java线程通信-LMLPHP

四、CountDownLatch

CountDownLatch 计数器适用于一个线程去等待多个线程的情况。例如A B C 三个线程同时运行,各自独立运行完后通知线程 D 执行,就可以利用 CountdownLatch 来实现这类通信方式。

对比之前的join方法,thread.join()可以让一个线程等另一个线程运行完毕后再继续执行,其可以在 D 线程里依次 join A B C,但这样 A B C 必须依次执行,无法实现ABC三者能同步运行。 

Java线程通信-LMLPHPJava线程通信-LMLPHP
 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、D
15         CountDownLatch latch = new CountDownLatch(3);
16         new Thread(new Runnable() {
17             @Override
18             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 C
32         for (char threadName = 'A'; threadName <= 'C'; threadName++) {
33             String name = String.valueOf(threadName);
34             new Thread(new Runnable() {
35                 @Override
36                 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 }
View Code

Java线程通信-LMLPHP

 五、CyclicBarrier 

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

Java线程通信-LMLPHP

 

螣蛇乘雾

终为土灰

03-27 21:31