一、前置知识

线程间通信三要素:

多线程+判断+操作+通知+资源类。

上面的五个要素,其他三个要素就是普通的多线程程序问题,那么通信就需要线程间的互相通知,往往伴随着何时通信的判断逻辑。

在 java 的 Object 类里就提供了对应的方法来进行通知,同样的,保证安全的判断采用隐式的对象锁,也就是 synchronized 关键字实现。这块内容在:

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

已经写过。

二、使用 Lock 实现线程间通信

那么,我们知道 juc 包里提供了显式的锁,即 Lock 接口的各种实现类,如果想用显式的锁来实现线程间通信问题,唤醒方法就要使用对应的 Conditon 类的 await 和 signalAll 方法。(这两个方法名字也能看得出来,对应 Object 类的 wait 和 notifyAll)

Condition 对象的获取可以通过具体的 Lock 实现类的对象的 newCondition 方法获得。

public class Communication2 {
public static void main(String[] args) {
Container container = new Container();
new Thread(()->{
for (int i = 0; i < 10; i++){
container.increment();
}
},"生产者1").start();
new Thread(()->{
for (int i = 0; i < 10; i++){
container.decrenment();
}
},"消费者1").start();
new Thread(()->{
for (int i = 0; i < 10; i++){
container.increment();
}
},"生产者2").start();
new Thread(()->{
for (int i = 0; i < 10; i++){
container.decrenment();
}
},"消费者2").start();
new Thread(()->{
for (int i = 0; i < 10; i++){
container.increment();
}
},"生产者3").start();
new Thread(()->{
for (int i = 0; i < 10; i++){
container.decrenment();
}
},"消费者3").start();
} } class Container{
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition(); public void increment(){
lock.lock();
try {
while (count != 0){
condition.await();
}
count++;
System.out.println(Thread.currentThread().getName() + " "+count);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} public void decrenment(){
lock.lock();
try{
while (count == 0){
condition.await();
}
count--;
System.out.println(Thread.currentThread().getName()+ " " + count);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

输出也没有任何问题。

这里面我们模拟了 3 个生产者和 3 个消费者,进行对一个资源类,其实就是一个数字 count 的操作,并使用 Condition 类来进行唤醒操作。

从目前代码的用法来看, juc 包的 Lock 接口实现类和之前使用 synchronized + Object 类的线程通信方法是一样的,但是并发包的开发工具给了他更多的灵活性。灵活在哪?

三、唤醒特定线程

新技术解决了旧问题,这个方法解决的问题就是,在生产者消费者问题里:有时候我们并不想唤醒所有的对面伙伴,而只想要唤醒特定的一部分,这时候该怎么办呢?

如果没有显式的 lock,我们的思路可能是:

  1. 采用一个标志对象,可以是一个数值或者别的;
  2. 当通信调用 signalAll 的时候,其他线程都去判断这个标志,从而决定自己应不应该工作。

这种实现是可行的,但是本质上其他线程都被唤醒,然后一直阻塞+判断,其实还是在竞争。那么 Condition 类其实就已经提供了对应的方法,来完成这样的操作:

我们看这样一个需求:

  • 同样是多线程操作、需要通信。但是我们要指定各个线程交替的顺序,以及指定唤醒的时候是指定哪个具体线程,这样就不会存在唤醒所有线程然后他们之间互相竞争了。
  • 具体说是:AA 打印 5 次,BB 打印 10 次, CC 打印 15次,然后接着从 AA 开始一轮三个交替

代码如下:

public class TakeTurnPrint {
public static void main(String[] args) {
ShareResource resource = new ShareResource();
new Thread(()->{resource.printA();},"A").start();
new Thread(()->{resource.printB();},"B").start();
new Thread(()->{resource.printC();},"C").start();
}
} /**
* 资源
*/
class ShareResource{
private int signal = 0;//0-A,1-B,2-C
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition(); public void printA(){
lock.lock();
try{
while (signal != 0){
condition.await();//精准
}
for (int i = 0; i < 5; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
signal = 1;//精准
condition1.signal();//精准指定下一个
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
} public void printB(){
lock.lock();
try{
while (signal != 1){
condition1.await();//精准
}
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
signal = 2;//精准
condition2.signal();//精准指定下一个
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
} public void printC(){
lock.lock();
try{
while (signal != 2){
condition2.await();//精准
}
for (int i = 0; i < 15; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
signal = 0;//精准
condition.signal();//精准指定下一个
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}

其中,使用三个 Condition 对象,用一个 signal 的不同值,来通知不同的线程。

juc包:使用 juc 包下的显式 Lock 实现线程间通信的更多相关文章

  1. Java多线程编程(6)--线程间通信(下)

      因为本文的内容大部分是以生产者/消费者模式来进行讲解和举例的,所以在开始学习本文介绍的几种线程间的通信方式之前,我们先来熟悉一下生产者/消费者模式.   在实际的软件开发过程中,经常会碰到如下场景 ...

  2. windows下进程间通信与线程间通信

    进程间通信: 1.文件映射(Memory-Mapped Files) 文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待.因此,进程不必使用文件I/ ...

  3. “全栈2019”Java多线程第三十一章:中断正在等待显式锁的线程

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  4. Windows环境下多线程编程原理与应用读书笔记(4)————线程间通信概述

    <一>线程间通信方法 全局变量方式:进程中的线程共享全局变量,可以通过全局变量进行线程间通信. 参数传递法:主线程创建子线程并让子线程为其服务,因此主线程和其他线程可以通过参数传递进行通信 ...

  5. JUC之线程间的通信

    线程通信 视频1: 2021.12.18 JUC视频学习片段 对上次多线程编程步骤补充(中部): 创建资源类,在资源类中创建属性和操作方法 在资源类里面操作 判断 干活 通知 创建多个线程,调用资源类 ...

  6. 浅析SQL查询语句未显式指定排序方式,无法保证同样的查询每次排序结果都一致的原因

    本文出处:http://www.cnblogs.com/wy123/p/6189100.html 标题有点拗口,来源于一个开发人员遇到的实际问题 先抛出问题:一个查询没有明确指定排序方式,那么,第二次 ...

  7. elasticsearch的store属性跟_source字段——如果你的文档长度很长,存储了_source,从_source中获取field的代价很大,你可以显式的将某些field的store属性设置为yes,否则设置为no

    转自:http://kangrui.iteye.com/blog/2262860 众所周知_source字段存储的是索引的原始内容,那store属性的设置是为何呢?es为什么要把store的默认取值设 ...

  8. 并发编程 19—— 显式的Conditon 对象

    Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...

  9. C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)

    介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...

随机推荐

  1. mysql必知必会——GROUP BY和HAVING

    mysql必知必会——GROUP BY和HAVING 创建表结构 create table `employ_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, ...

  2. Educational Codeforces Round 65 (Rated for Div. 2)(ACD)B是交互题,不怎么会

    A. Telephone Number A telephone number is a sequence of exactly 11 digits, where the first digit is  ...

  3. Brackets(括号最大匹配问题(区间dp))

    We give the following inductive definition of a “regular brackets” sequence: the empty sequence is a ...

  4. 面试【JAVA基础】集合类

    1.ArrayList的扩容机制 每次扩容是原来容量的1.5倍,通过移位的方法实现. 使用copyOf的方式进行扩容. 扩容算法是首先获取到扩容前容器的大小.然后通过oldCapacity (oldC ...

  5. jQuery的那些事儿

    jQuery概述 j-JavaScript+Query就是查询js的库,把js中的DOM操作做了封装,实现快速查询使用其中的功能. 优化了DOM操作.事件处理.动画设计和Ajax交互 学习jQuery ...

  6. [LeetCode]671. 二叉树中第二小的节点(递归)

    题目 给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0.如果一个节点有两个子节点的话,那么这个节点的值不大于它的子节点的值. 给出这样的一个二叉树,你需要输出所有 ...

  7. [算法题]最小m划分(划分DP)

    题目描述 给定 n 个整数组成的序列,将序列分割为 m 段,如何分割才能使这 m 段子序列的和的最大值达到最小? 题解 状态表示 dp[i][j]表示前i个元素划分j段 子序列和的最大值的最小值 状态 ...

  8. hadoop文件系统上的小文件合并-Hadoop Archives

    1. 什么是Hadoop archives Hadoop archives是特殊的档案格式.一个Hadoop archive对应一个文件系统目录. Hadoop archive的扩展名是.har.Ha ...

  9. tagCould3d 移动端优化版

    针对https://github.com/bitjjj/JS-3D-TagCloud这个版本的做了移动端性能优化(使用transform做偏移及缩放,优化帧).基本原理一致. class TagCou ...

  10. 如何解决Python下 pip install module 下载慢解决方法?

    对于Python来编程的用户最大的一个痛点就是,下载模块是下载速度特别慢,那么有没有解决方法呢? 换Python的pip下载源 1.首先安装一个模块 pqi,在cmd下 pip install pqi ...