一、前置知识

线程间通信三要素:

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

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

在 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. unity 模板测试 详解

    https://blog.csdn.net/u011047171/article/details/46928463#t4

  2. unity3d中的Quaternion.LookRotation

    android开发范例中的第二个粒子,是摇杆操作游戏,模式类似于“迷你高尔”,僵尸包围类型的设计游戏. 其中让我注意到这个函数的使用非常特别:Quaternion.LookRotation. 游戏针对 ...

  3. 写shader小细节——这个会不断更新

    这个是因为自己被自己蠢哭了动笔的,里面大概记录自己所犯的错,和一些小知识. 1.有一个错误我经常犯:内部定义的字段没对应开放到编辑器的字段.这个是由于我有点依赖ide写代码的习惯导致,而shader的 ...

  4. 剑指 Offer 46. 把数字翻译成字符串

    题目描述 给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 "a" ,1 翻译成 "b",--,11 翻译成 "l",--,25 ...

  5. Spring security OAuth2.0认证授权学习第二天(基础概念-RBAC)

    RBAC 基于角色的访问控制 基于角色的访问控制用代码实现一下其实就是一个if的问题if(如果有角色1){ } 如果某个角色可以访问某个功能,当某一天其他的另一个角色也可以访问了,那么代码就需要变化, ...

  6. C010:书号分解ISBN

    代码: #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { int prefix,groupIndentifier ...

  7. 阿里云短信服务验证码封装类 - PHP

    本文记录在ThinkPHP6.0中使用阿里云短信验证码,该封装类不仅仅局限于TP,拿来即用 使用该类之前必须引入 flc/dysms 扩展,该封装类就是基于这个扩展写的 composer requir ...

  8. Linux:apache目录结构和配置文件详解

    bin目录下的常见命令 conf目录 htdocs目录 logs目录 httpd.conf文件解析. 如果后期自己新创建了新的站点目录,就要重新增加对应的目录权限配置 extra/目录下配置文件解析 ...

  9. HTML -- 表单元素2

    (1)<select>元素(下拉列表) <html> <body> <!-- 表单处理程序在action属性中指定 --> <form actio ...

  10. 第9课 - const 和 volatile分析

    第9课 - const和volatile分析 1. const只读变量 (1)const修饰的变量是只读的,本质上还是变量,并不是真正意义上的常量         ※※ const只是告诉编译器该变量 ...