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*/ 10private static void situationOne() { 11Thread a = new Thread(new Runnable() { 12@Override 13public void run() { 14doSomething("a"); 15} 16}); 17Thread b = new Thread(new Runnable() { 18@Override 19public void run() { 20doSomething("b"); 21} 22}); 23a.start(); 24b.start(); 25} 26 27/** 28* 依次打印 1, 2, 3 三个数字 29* 30* @param threadName 31*/ 32private static void doSomething(String threadName) { 33int i = 0; 34while (i++ < 3) { 35try { 36Thread.sleep(200); 37} catch (InterruptedException e) { 38e.printStackTrace(); 39} 40System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + i); 41} 42} 43 44public static void main(String[] args) { 45situationOne(); 46} 47 }
View Code 多次运行发现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执行完毕后才轮到b 10*/ 11private static void situationOne() { 12Thread a = new Thread(new Runnable() { 13@Override 14public void run() { 15doSomething("a"); 16} 17}); 18Thread b = new Thread(new Runnable() { 19@Override 20public void run() { 21try { 22System.out.println("线程 b 正在通过thread.join()等待线程 a 执行完毕后再润"); 23// thread.join() 在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行,即必须a执行完毕后才轮到b 24a.join(); 25} catch (InterruptedException e) { 26e.printStackTrace(); 27} 28doSomething("b"); 29} 30}); 31a.start(); 32b.start(); 33} 34 35/** 36* 依次打印 1, 2, 3 三个数字 37* 38* @param threadName 39*/ 40private static void doSomething(String threadName) { 41int i = 0; 42while (i++ < 3) { 43try { 44Thread.sleep(200); 45} catch (InterruptedException e) { 46e.printStackTrace(); 47} 48System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + i); 49} 50} 51 52public static void main(String[] args) { 53situationOne(); 54} 55 }
View Code 无论运行多少次,都是线程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 7private static void situationTwo() { 8// a 和 b 的共享对象锁 lock 9Object lock = new Object(); 10Thread a = new Thread(new Runnable() { 11@Override 12public void run() { 13// 同步锁 lock 14synchronized (lock) { 15// a 获得锁后执行 16doSomething("a", 1); 17try { 18// 调用 lock.wait() 方法,交出锁的控制权,进入 wait 状态,等待notify唤醒 19lock.wait(); 20} catch (InterruptedException e) { 21e.printStackTrace(); 22} 23doSomething("a", 2); 24doSomething("a", 3); 25} 26} 27}); 28Thread b = new Thread(new Runnable() { 29@Override 30public void run() { 31// 同步锁 lock 32synchronized (lock) { 33// a 获得锁后执行 34doSomething("b", 1); 35doSomething("b", 2); 36doSomething("b", 3); 37// 调用 lock.notify() 方法,唤醒正在 wait 的线程 a 38lock.notify(); 39} 40} 41}); 42a.start(); 43b.start(); 44} 45 46/** 47* 打印 48* 49* @param threadName 50* @param num 51*/ 52private static void doSomething(String threadName, int num) { 53System.out.println(LocalDate.now() + " Thread " + threadName + " is doing, printing: " + num); 54} 55 56public static void main(String[] args) { 57situationTwo(); 58} 59 }
View Code 无论运行多少次,都是线程a先执行打印1,然后线程b执行打印1、2、3,最后线程a执行打印2、3。
四、CountDownLatch
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*/ 13private static void situationThree() { 14// 初始计数值设置为3,即总共四个线程A、B、C、D 15CountDownLatch latch = new CountDownLatch(3); 16new Thread(new Runnable() { 17@Override 18public void run() { 19System.out.println(LocalDate.now() + "线程 D 等待线程A B C 执行完毕后才可执行"); 20try { 21// await() 检查计数器值是否为 0,若不为 0 则保持等待状态 22latch.await(); 23// 其他线程 的 countDown() 方法把计数值变成 0 时,等待线程里的 countDownLatch.await() 立即退出,继续执行下面的代码 24System.out.println(LocalDate.now() + "线程A B C 执行完毕,轮到线程D 执行了,当前latch:" + latch.getCount()); 25} catch (InterruptedException e) { 26e.printStackTrace(); 27} 28} 29}).start(); 30 31// 循环执行线程 A B C 32for (char threadName = 'A'; threadName <= 'C'; threadName++) { 33String name = String.valueOf(threadName); 34new Thread(new Runnable() { 35@Override 36public void run() { 37System.out.println("线程 " + name + " is running"); 38// countDown(),将倒计数器减 1, 计数器被减至 0 时立即触发D 的 await() 39try { 40Thread.sleep(200); 41} catch (InterruptedException e) { 42e.printStackTrace(); 43} 44System.out.println("线程 " + name + " 执行完毕计数器"); 45latch.countDown(); 46} 47}).start(); 48} 49} 50 51public static void main(String[] args) { 52situationThree(); 53} 54 55 }
View Code
五、CyclicBarrier
实现线程间互相等待,可以利用 CyclicBarrier 栅栏。CountDownLatch 可以用来倒计数,但当计数完毕,只有一个线程的 await() 会得到响应,无法让多个线程同时触发。如要求线程 A B C 各自开始准备,直到三者都准备完毕再同时运行其就无法满足需求,而用CyclicBarrier则完全OK。
螣蛇乘雾
终为土灰
【Java线程通信】