在多线程系统中,彼此之间的通信协作非常重要,下面来聊聊线程间通信的几种方式。

wait/notify

想像一个场景,A、B两个线程操作一个共享List对象,A对List进行add操作,B线程等待List的size=500时就打印记录日志,这要怎么处理呢?

一个办法就是,B线程while (true) { if(List.size == 500) {打印日志} },这样两个线程之间就有了通信,B线程不断通过轮训来检测 List.size == 500 这个条件。

这样可以实现我们的需求,但是也带来了问题:CPU把资源浪费了B线程的轮询操作上,因为while操作并不释放CPU资源,导致了CPU会一直在这个线程中做判断操作。

这要非常浪费CPU资源,所以就需要有一种机制来实现减少CPU的资源浪费,而且还可以实现在多个线程间通信,它就是“wait/notify”机制。

定义两个线程类:

public class MyThread1_1 extends Thread {

    private Object lock;

    public MyThread1_1(Object lock) {
this.lock = lock;
} public void run() {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "开始------wait time = " + System.currentTimeMillis());
lock.wait();
System.out.println(Thread.currentThread().getName() + "开始------sleep time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "结束------sleep time = " + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + "结束------wait time = " + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

  

public class MyThread1_2 extends Thread {

    private Object lock;

    public MyThread1_2(Object lock) {
this.lock = lock;
} public void run() {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "开始------notify time = " + System.currentTimeMillis());
lock.notify();
System.out.println(Thread.currentThread().getName() + "开始------sleep time = " + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "结束------sleep time = " + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + "结束------notify time = " + System.currentTimeMillis());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

  

  测试方法,myThread1先执行,然后sleep 一秒后,myThread2再执行

@Test
public void test1() throws InterruptedException {
Object object = new Object();
MyThread1_1 myThread1_1 = new MyThread1_1(object);
MyThread1_2 myThread1_2 = new MyThread1_2(object);
myThread1_1.start();
Thread.sleep(1000);
myThread1_2.start(); myThread1_1.join();
myThread1_2.join(); }

  执行结果:

Thread-0开始------wait time = 1639464183921
Thread-1开始------notify time = 1639464184925
Thread-1开始------sleep time = 1639464184925
Thread-1结束------sleep time = 1639464186928
Thread-1结束------notify time = 1639464186928
Thread-0开始------sleep time = 1639464186928
Thread-0结束------sleep time = 1639464188931
Thread-0结束------wait time = 1639464188931

  可以看到第一行和第二行 开始执行之间只间隔了1s,说明wait方法确实进入等待,

而且没有继续执行wait后面的sleep 2秒,而是执行了notify方法,说明wait方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。

第二行和第五行间隔2秒钟,说明notify方法不会释放共享资源的锁。

第6行 说明notify执行完后,唤醒了刚才wait的线程,从而继续执行后面的sleep方法。

说明notify方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个”线程。

另外还有notifyAll()方法可以使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。

此时,优先级最高的那个线程最先执行,但也有可能是随机执行,因为这要取决于JVM虚拟机的实现。

方法join

前面的测试方法中几乎都使用了join方法,那么这个方法到底起到什么作用呢?

在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束,

所以在主线程中使用join方法的作用就是让主线程等待子线程线程对象销毁。

/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

  看下jdk API的源码可以看到,其实join内部使用的还是wait方法进行等待,

join(long millis)方法的一个重点是要区分出和sleep(long millis)方法的区别:

sleep(long millis)不释放锁,join(long millis)释放锁,因为join方法内部使用的是wait(),因此会释放锁。join()其实就是join(0)而已。

ThreadLocal类

ThreadLocal不是用来解决共享对象的多线程访问问题的,而是实现每一个线程都维护自己的共享变量,起到线程隔离的作用。

关于ThreadLocal源码分析可以参考这篇文章:https://www.cnblogs.com/xrq730/p/4854813.html

下面看个ThreadLocal的例子:

public class Tools {

    public static ThreadLocal<Object> tl = new ThreadLocal<Object>();

}

  两个线程类,分别向ThreadLocal里设置值

public class MyThread1_1 extends Thread {

    @Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
Tools.tl.set("ThreadA" + (i + 1));
System.out.println("ThreadA get Value=" + Tools.tl.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class MyThread1_2 extends Thread {

    @Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
Tools.tl.set("ThreadB" + (i + 1));
System.out.println("ThreadB get Value=" + Tools.tl.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

  

@Test
public void test1() {
try {
MyThread1_1 a = new MyThread1_1();
MyThread1_2 b = new MyThread1_2();
a.start();
b.start();
a.join();
b.join();
} catch (Exception e) {
e.printStackTrace();
}
}

  执行结果:

ThreadB get Value=ThreadB1
ThreadA get Value=ThreadA1
ThreadA get Value=ThreadA2
ThreadB get Value=ThreadB2
ThreadA get Value=ThreadA3
ThreadB get Value=ThreadB3
ThreadA get Value=ThreadA4
ThreadB get Value=ThreadB4
ThreadB get Value=ThreadB5
ThreadA get Value=ThreadA5
ThreadB get Value=ThreadB6
ThreadA get Value=ThreadA6
ThreadB get Value=ThreadB7
ThreadA get Value=ThreadA7
ThreadB get Value=ThreadB8
ThreadA get Value=ThreadA8
ThreadA get Value=ThreadA9
ThreadB get Value=ThreadB9
ThreadB get Value=ThreadB10
ThreadA get Value=ThreadA10

  可以看到两个线程取出的值没有重复也没有互相影响,其实它内部变化的只是线程本身的 ThreadLocalMap。

感兴趣的还可以去看看 InheritableThreadLocal,它可以在子线程中取得父线程继承下来的值。

参考文献

1:《Java并发编程的艺术》

2:《Java多线程编程核心技术》

java多线程5:线程间的通信的更多相关文章

  1. Java多线程中线程间的通信

    一.使用while方式来实现线程之间的通信 package com.ietree.multithread.sync; import java.util.ArrayList; import java.u ...

  2. java多线程与线程间通信

    转自(http://blog.csdn.net/jerrying0203/article/details/45563947) 本文学习并总结java多线程与线程间通信的原理和方法,内容涉及java线程 ...

  3. iOS开发多线程篇—线程间的通信

    iOS开发多线程篇—线程间的通信 一.简单说明 线程间通信:在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信 线程间通信的体现 1个线程传递数据给另1个线程 在1个线程中执行完特定任 ...

  4. Java——多线程之线程间通信

    Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...

  5. Java多线程编程-线程之间的通信

    转载自:这里 学习了基础的线程知识 看到了 线程之间的通信 线程之间有哪些通信方式呢? 1.同步 这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信. public ...

  6. JAVA多线程之线程间的通信方式

    (转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...

  7. Java多线程基础——线程间通信

    在使用多线程的时候,经常需要多个线程进行协作来完成一件事情.在前面两章分析了Java多线程的基本使用以及利用synchronized来实现多个线程同步调用方法或者执行代码块.但上面两章的内容涉及到的例 ...

  8. Java多线程:线程间通信之volatile与sychronized

    由前文Java内存模型我们熟悉了Java的内存工作模式和线程间的交互规范,本篇从应用层面讲解Java线程间通信. Java为线程间通信提供了三个相关的关键字volatile, synchronized ...

  9. Java多线程:线程间通信之Lock

    Java 5 之后,Java在内置关键字sychronized的基础上又增加了一个新的处理锁的方式,Lock类. 由于在Java线程间通信:volatile与sychronized中,我们已经详细的了 ...

  10. java多线程:线程间通信——生产者消费者模型

    一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...

随机推荐

  1. N体模拟数据可视化 LightningChart®

    N体模拟数据可视化 LightningChart ​ N体模拟也许是目前最先进的数据可视化类型之一.事实上,我们现在谈论的不再是以商业为中心的传统数据的可视化,现在它甚至超越了比如振动分析等先进数据源 ...

  2. 躺平吧,平铺的窗口「GitHub 热点速览 v.21.47」

    作者:HelloGitHub-小鱼干 用 macOS 系统经常会遇到的一个问题便是多开窗口如何快速找寻的问题,本周特推项目 yabai 便是来解决这个问题的.直接把所有窗口平铺,是不是很"正 ...

  3. [cf741C]Arpa’s overnight party and Mehrdad’s silent entering

    直接令2i-1和2i的位置不相同,相当于有2n条边,对其进行二分图染色即可(这张图一定不存在奇环). 假设给出的n条关系是A类边,2i-1和2i的边是B类边,可以发现一条路径一定是AB交替(因为A/B ...

  4. Go语言核心36讲(Go语言实战与应用十三)--学习笔记

    35 | 并发安全字典sync.Map (下) 我们在上一篇文章中谈到了,由于并发安全字典提供的方法涉及的键和值的类型都是interface{},所以我们在调用这些方法的时候,往往还需要对键和值的实际 ...

  5. Jenkins快速上手安装

    目录 环境准备 - JDK 安装 1. APT 安装 2. WAR包方式运行 3.Docker 方式运行 Jenkins 是一个独立的开源自动化服务器,可以用来自动化与构建.测试.交付或部署软件相关的 ...

  6. Spring Cloud Gateway过滤器精确控制异常返回(实战,控制http返回码和message字段)

    欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 前文<Spring Cloud Gat ...

  7. BZOJ3971 [WF2013]Матрёшка

    *XXXIV. BZOJ3971 [WF2013]Матрёшка 摘自 DP 做题记录 II 例题 XXXIV. 仍然是神仙区间 DP. 直接设状态 \(f_{i,j}\) 表示区间 \([i,j] ...

  8. DirectX12 3D 游戏开发与实战第七章内容(下)

    利用Direct3D绘制几何体(续) 学习目标 学会一种无须每帧都要刷新命令队列的渲染流程,由此来优化程序的性能 了解另外两种跟签名参数类型:根描述符和根常量 探索如何在程序中生成和绘制常见的几何体, ...

  9. Oracle-创建新表,创建备份表,对表中插入多条数据

    一.创建新表 0.基本语法 create table 表名称(id varchar2(50) primary key ,name char(200) not null,phone number(11) ...

  10. SpringBoot整合Shiro 二:Shiro配置类

    环境搭建见上篇:SpringBoot整合Shiro 一:搭建环境 Shiro配置类配置 shiro的配置主要集中在 ShiroFilterFactoryBean 中 关于权限: anon:无需认证就可 ...