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 @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 }

多次运行发现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 */
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 }

无论运行多少次,都是线程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 @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 }

无论运行多少次,都是线程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 */
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 }

 五、CyclicBarrier 

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

螣蛇乘雾

终为土灰

												

Java线程通信的更多相关文章

  1. 第23章 java线程通信——生产者/消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

  2. Java 线程通信

    线程通信用来保证线程协调运行,一般在做线程同步的时候才需要考虑线程通信的问题. 1.传统的线程通信 通常利用Objeclt类提供的三个方法: wait() 导致当前线程等待,并释放该同步监视器的锁定, ...

  3. java线程通信与协作小结 多线程中篇(十六)

      在锁与监视器中我们对Object中的方法进行了简单介绍 以监视器原理为核心,三个方法:wait,notify.notifyAll,可以完成线程之间的通信 当然,不会像“语言”似的,有多种多样的沟通 ...

  4. Java线程通信-生产者消费者问题

    线程通信示例——生产者消费者问题 这类问题描述了一种情况,假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费.假设仓库中没有产品,则生产者可以将 产品放入仓库,有 ...

  5. Java线程通信——wait() 和 notify()

    Object类中有关线程通信的方法有两个notify方法和三个wait方法,官方解释: void notify() Wakes up a single thread that is waiting o ...

  6. JAVA线程通信之生产者与消费者

    package cn.test.hf.test3; import java.util.concurrent.locks.Condition;import java.util.concurrent.lo ...

  7. Java线程入门第二篇

    Java线程通信方法 0.(why)每个线程都有自己的栈空间,我们要线程之间进行交流,合作共赢. 1.synchronized和volatile关键字 a)  看下面的synchronized关键字 ...

  8. java多线程-线程通信

    线程通信的目标是使线程间能够互相发送信号.另一方面,线程通信使线程能够等待其他线程的信号. 通过共享对象通信 忙等待 wait(),notify()和 notifyAll() 丢失的信号 假唤醒 多线 ...

  9. Java核心知识点学习----使用Condition控制线程通信

    一.需求 实现线程间的通信,主线程循环3次后,子线程2循环2次,子线程3循环3次,然后主线程接着循环3次,如此循环3次. 即:A->B->C---A->B->C---A-> ...

随机推荐

  1. uniapp 微信发送订阅消息

    这篇主要针对小程序进行演示,既然是发送消息,那么就有三个问题.发送什么内容,给谁发送,怎么发送!往下一条一条解决. 发送什么消息内容 - 通过微信公众号平台 选择对应的消息模板 选择以后在我的模板里面 ...

  2. VMware Workstation批量克隆虚拟机

    由于经常要用vmware创建虚拟机做一些测试,集群的测试使用连接克隆,可以节省磁盘的空间(如果不是因为穷,没人愿意向生活低头) 于是找到了这个bat脚本,做了一些修改和学习,为大家加上了一些注释,方便 ...

  3. 关于mybatis,需要掌握的基础

    目录 ❀ 总结 mybatis,需要掌握的基础如下: 1.了解ORM 思想.ORM思想的作用.映射配置的两种方式 2.MyBatis开发流程(基本使用) 3.日志框架 4.了解mybatis生命周期并 ...

  4. Dubbo源码剖析六之SPI扩展点的实现之getExtension

    上文Dubbo源码剖析六之SPI扩展点的实现之getExtensionLoader - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中分析了getExtensionLoader,本文继续分 ...

  5. 从命令模式的维度理解Spring 之Application Event

    Spring的事件(Application Event)为Bean与Bean之间的信息通讯提供了支持.当一个Bean处理完一个任务之后,希望另一Bean指定并能做相应的处理,这时我们就需要让另外一个B ...

  6. 微服务从代码到k8s部署应有尽有系列(六、订单服务)

    我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...

  7. SpringBoot中请求参数 @MatrixVariable 矩阵变量

    一.矩阵变量请求格式 /users;id=1,uname=jack 二.SpringBoot开启矩阵请求 首先查看springboot源码关于矩阵部分的内容 在 WebMvcAutoConfigura ...

  8. Harbor2.2.4在CentOS7.9安装、部署

    CentOS7.9基础环境配置 https://www.cnblogs.com/uncleyong/p/15471002.html 直接从网盘获取配置好的环境 修改:vim /etc/hosts 12 ...

  9. 【代码编译器】vscode 配置详细介绍

    前言:运行环境.net6.0 C#10 安装.NET Core SDK和运行 官网下载地址:https://www.microsoft.com/net/download/core 安装.Net 4.7 ...

  10. Hadoop配置文件汇总

    (一)基本集群配置 共8个配置文件 hadoop-env.sh yarn-env.sh mapred-env.sh core-site.xml hdfs-site.xml yarn-site.xml ...