juc包:使用 juc 包下的显式 Lock 实现线程间通信
一、前置知识
线程间通信三要素:
多线程+判断+操作+通知+资源类。
上面的五个要素,其他三个要素就是普通的多线程程序问题,那么通信就需要线程间的互相通知,往往伴随着何时通信的判断逻辑。
在 java 的 Object 类里就提供了对应的方法来进行通知,同样的,保证安全的判断采用隐式的对象锁,也就是 synchronized 关键字实现。这块内容在:
已经写过。
二、使用 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,我们的思路可能是:
- 采用一个标志对象,可以是一个数值或者别的;
- 当通信调用 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 实现线程间通信的更多相关文章
- Java多线程编程(6)--线程间通信(下)
因为本文的内容大部分是以生产者/消费者模式来进行讲解和举例的,所以在开始学习本文介绍的几种线程间的通信方式之前,我们先来熟悉一下生产者/消费者模式. 在实际的软件开发过程中,经常会碰到如下场景 ...
- windows下进程间通信与线程间通信
进程间通信: 1.文件映射(Memory-Mapped Files) 文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待.因此,进程不必使用文件I/ ...
- “全栈2019”Java多线程第三十一章:中断正在等待显式锁的线程
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- Windows环境下多线程编程原理与应用读书笔记(4)————线程间通信概述
<一>线程间通信方法 全局变量方式:进程中的线程共享全局变量,可以通过全局变量进行线程间通信. 参数传递法:主线程创建子线程并让子线程为其服务,因此主线程和其他线程可以通过参数传递进行通信 ...
- JUC之线程间的通信
线程通信 视频1: 2021.12.18 JUC视频学习片段 对上次多线程编程步骤补充(中部): 创建资源类,在资源类中创建属性和操作方法 在资源类里面操作 判断 干活 通知 创建多个线程,调用资源类 ...
- 浅析SQL查询语句未显式指定排序方式,无法保证同样的查询每次排序结果都一致的原因
本文出处:http://www.cnblogs.com/wy123/p/6189100.html 标题有点拗口,来源于一个开发人员遇到的实际问题 先抛出问题:一个查询没有明确指定排序方式,那么,第二次 ...
- elasticsearch的store属性跟_source字段——如果你的文档长度很长,存储了_source,从_source中获取field的代价很大,你可以显式的将某些field的store属性设置为yes,否则设置为no
转自:http://kangrui.iteye.com/blog/2262860 众所周知_source字段存储的是索引的原始内容,那store属性的设置是为何呢?es为什么要把store的默认取值设 ...
- 并发编程 19—— 显式的Conditon 对象
Java并发编程实践 目录 并发编程 01—— ThreadLocal 并发编程 02—— ConcurrentHashMap 并发编程 03—— 阻塞队列和生产者-消费者模式 并发编程 04—— 闭 ...
- C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)
介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...
随机推荐
- Unity调用PC摄像头
转载于Unity3d圣典里面,具体哪位大侠写的我忘咯. using UnityEngine; using System.Collections; public class CameraTest : M ...
- Mybatis实例及配置(一)
创建实体类: package com.test.mybatis.bean; public class Employee { private Integer id; private String las ...
- ELK入门及基本使用
预备知识-Restful 起源 在没有前后端分离概念之前,一个网站的完成总是“all in one”,在这个阶段,页面.数据.渲染全部在服务端完成,这样做的最大的弊端是后期维护,扩展极其痛苦,开发人员 ...
- OneDrive Weblist
OneIndex-Serverless 教程:https://zhuanlan.zhihu.com/p/74538287 https://github.com/LiuChangFreeman/OneI ...
- 《Linux 操作系统》Linux的常用命令操作大全
前言 在学习命令之前先学习我们该如何去学习linux 命令. 几乎每一个命令都有参数,每个参数的含义是什么,我们一般也不是全部都能记住,所以我们必须有一个可以知道每一个命令下各个参数的含义的方法. 命 ...
- 尤雨溪:TypeScript不会取代JavaScript
来源 |evrone.com译者 | 核子可乐策划 | 蔡芳芳 近日,Evrone 与 Vue.js 的作者尤雨溪进行了一次访谈,了解他对于无后端与全栈方法.以及 Vue.js 适用场景的看法,还有他 ...
- Python反转链表
# -*- coding:utf-8 -*- # class ListNode: # def __init__(self, x): # self.val = x # self.next = None ...
- docker部署Broketrmq集群
部署Broketrmq集群 通过docker-compose形式部署 首先创建 broker 配置文件,配置文件如下: brokerClusterName = DefaultCluster #集群名 ...
- spring mvc(4) HandlerMapping
在前面一节里提到,DispatcherServlet在接收到请求后,通过HandlerMapping找到处理请求对应的Controller(其实处理请求器并不一定是Controller,还可以是Htt ...
- Java并发包之Executors
概述 Executor.ExecutorService.ScheduledExecutorService.ThreadFactory.Callable的工厂和工具类. 方法 构造一个固定线程数目的线程 ...