Java线程间通信方式(2)

news2025/1/1 23:47:38

前文了解了线程通信方式中的Object.wait/Object.notify以及Semaphore,接下来我们继续了解其他的线程间通信方式。

CountDownLatch

CountDownLatch利用一个指定的计数初始化,由于调用了countDown方法,await方法会阻塞直到当前技术为0,之后所有等待的线程都会被释放,任何后续的await都会立即返回。

CountDownLatch是一个通用的同步工具,可以用于许多目的。如果CountDownLatch初始化的计数为1,则可以用作简单的开/关锁存器或门,也就意味着所有调用await的线程都在门处等待,直到由调用countDown()的线程打开它。一个初始化为N的CountDownLatch可以用来让一个线程等待,直到N个线程完成了某个动作,或者某个动作完成了N次。

CountDownLatch核心接口说明如下:

方法名称描述备注
await()使当前线程进入等待状态,直到计数减为0或者当前线程被中断,否则不会唤醒/
await(long timeout, TimeUnit unit)等待timeout时间后,计数还没减成0,则等待线程唤醒继续执行,不再等待/
countDown()对计数减1,如果减到了0,就会唤醒等待的线程/
getCount()获取当前计数的值/

举个简单例子,某主任务中一共要发起5个子任务,等待所有任务完成后主任务继续,此时在主任务执行线程以计数取值为5初始化CountDownLatch,调用await等待,在每个子任务完成时,调用countDown方法使计数减1,等到5个子任务全部完成后,此时计数减为0,主任务唤醒,继续执行。

通过例子和描述可以看出CountDownLatch属于一次性操作,计数无法重置。

以主任务中发起5个子任务为例,使用CountDownLatch的代码实现如下:

public static void main(String[] args) {
    Thread mainThread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("MainThread start working");
            CountDownLatch countDownLatch = new CountDownLatch(5);
            try {
                for (int i=0;i<5;i++) {
                    Thread subThread = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println(Thread.currentThread().getName()+" start working");
                            System.out.println(Thread.currentThread().getName()+" completed");
                            countDownLatch.countDown();
                        }
                    });
                    subThread.setName("SubThread"+(i+1));
                    subThread.start();
                }
                countDownLatch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("MainThread completed");
        }
    });
    mainThread.setName("MainThread");
    mainThread.start();
}

运行结果如下:

1-4-3-20

对于CountDownLatch 而言,如果在计数为0前,有子任务发生异常导致退出,则所有等待的线程都会一致等待,直到超时时间来临,所以使用CountDownLatch一定要注意线程异常的处理。

CyclicBarrier

CyclicBarrier同样是一个多线程同步的工具类,译为循环栅栏,其允许一组线程互相等待到达一个同步的屏障点,CyclicBarrier在一组固定大小的线程中存在相互等待的场景下十分有用,之所以称为循环栅栏,是因为其在其他线程释放后可以重新使用。

CyclicBarrier使用一个指定计数和Runnable进行初始化,初始化完成后,当CyclicBarrier.await调用次数等于计数,也就是等待线程数等于计数时,则会触发初始化传入Runnable运行,该Runnable运行在最后一个进入等待的线程中,随后所有等待线程唤醒继续执行。

7位选手参加田径比赛,所有人在起点就位,准备完成后,发射信号枪起跑。用CyclicBarrier来实现比赛逻辑,则以计数7和运行发射信号枪的Runnable初始化CyclicBarrier,每一个选手起点准备完成后,都调用一次CyclicBarrier.await,当所有选手都准备完成,发射信号枪的Runnable运行,比赛开始,实现代码如下:

public static void main(String[] args) {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(7, new Runnable() {
        @Override
        public void run() {
            System.out.println("所有选手准备完成,发射信号枪,比赛开始,运行在:"+Thread.currentThread().getName());
        }
    });
    for (int i=0;i<7;i++) {
        int finalI = i;
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println((finalI +1)+"号选手准备完成,运行在:"+Thread.currentThread().getName());
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println((finalI +1)+"号选手准备达到准点,运行在:"+Thread.currentThread().getName());
            }
        });
        thread.setName("选手"+(i+1));
        thread.start();
    }
}

输出如下:

1-4-3-21

对于CyclicBarrier 而言,如果在所有线程都到达屏障陷入阻塞前,如果有线程发生异常导致未到达栅栏提前退出,则所有等待在栅栏都会以BrokenBarrierException或InterruptedException异常退出。

Lock,Condition与ReentrantLock

Lock通常译为锁,其是一种控制多个线程对共享资源访问的工具,与同步方法和语句相比,以接口定义,在其内部提供了更广泛的锁操作,其允许的关联结构更灵活,比如不同的属性,多个关联的Condition对象等。通常情况下,可以通过锁控制一次只有一个线程可以访问共享资源,所有线程在访问共享资源时都需要获取锁。部分锁也支持多共享资源进行并发访问,比如ReadWriteLock。

Lock接口核心函数如下表所示:

函数名称描述备注
lock()获取对象锁,如果锁不可用,则当前线程被阻塞,在获取锁前处于休眠状态/
unlock()释放锁,锁释放操作应处于finally块内,确保在任何情况下都能释放,以免造成死锁/
tryLock()锁是否可用,true-可用,false-不可用/

Condition通常译为条件,其将Object的wait,notify,notifyAll提取到不同的对象中,通过将这些对象与任一锁对象的使用结合起来,从而达到单个对象具有多个等待集的效果,在Lock+Condition的模式中,Lock代替了前文中Object.wait+synchronized中的synchronized,Condition代替了Object.wait/notify/notifyAll。

Condition核心函数如下表所示:

函数名称描述备注
await()当前线程进入等待状态,直到被通知或中断,线程从await方法返回进入运行状态的情况包括:1.其他线程调用了该Condition的signal或signalAll方法。2.其他线程中断当前线程。/
signal()唤醒一个等待在该Condition上的线程/
signalAll()唤醒所有等待在该Condition上的线程/

在实现上Lock和Condition均为接口,所以我们一般使用Lock的实现类ReentrantLock来实现锁机制,借助ReentrantLock对象内部的newCondition获得Condition的实现对象,进而搭配完成线程间通信,接下来我们使用ReentrantLock实现消费者-生产者模式,代码如下:

private static int count = 0;
public static void main(String[] args) {
    // 创建Lock对象
    ReentrantLock lock = new ReentrantLock();
    // 创建Condition对象
    Condition condition = lock.newCondition();
    // 创建盘子
    Counter counter = new Counter();
    Thread incThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (count < 4) {
                lock.lock();
                try {
                    if (counter.getCount() != 0) {
                        condition.await();
                    }
                    counter.incCount();
                    count++;
                    System.out.println("Inc Thread ++,current count is:" + counter.getCount());
                    condition.signalAll();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }
            }
        }
    });
    incThread.setName("Inc Thread");
    incThread.start();

    Thread decThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (count < 4) {
                lock.lock();
                try {
                    if (counter.getCount() == 0) {
                        condition.await();
                    }
                    counter.decCount();
                    System.out.println("Dec Thread --,current count is:" + counter.getCount());
                    condition.signalAll();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }
            }
        }
    });
    decThread.setName("Dec Thread");
    decThread.start();
}

运行结果如下:

1-4-3-23

前面提到过,Condition将Object的wait,notify,notifyAll提取到不同的对象中,也就意味着我们可以更灵活的控制锁获取,我们新建两个Condition对象,一个控制生产者,一个控制消费者,代码如下:

private static int count = 0;
public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();
    // 控制生产者
    Condition incCondition = lock.newCondition();
    // 控制消费者
    Condition decCondition = lock.newCondition();
    Counter counter = new Counter();
    Thread incThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (count < 4) {
                lock.lock();
                try {
                    if (counter.getCount() != 0) {
                        // 阻塞当前生产线程
                        incCondition.await();
                    }
                    counter.incCount();
                    count++;
                    System.out.println("Inc Thread ++,current count is:" + counter.getCount());
                    // 唤醒消费者
                    decCondition.signalAll();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }
            }
        }
    });
    incThread.setName("Inc Thread");
    incThread.start();

    Thread decThread = new Thread(new Runnable() {
        @Override
        public void run() {
            while (count < 4) {
                lock.lock();
                try {
                    if (counter.getCount() == 0) {
                        // 阻塞当前消费线程
                        decCondition.await();
                    }
                    counter.decCount();
                    System.out.println("Dec Thread --,current count is:" + counter.getCount());
                    // 唤醒生产线程
                    incCondition.signalAll();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }
            }
        }
    });
    decThread.setName("Dec Thread");
    decThread.start();
}

运行结果如下:

1-4-3-22

当然ReentrantLock也可以不搭配Condition独立使用的,通过lock函数获取锁,通过unlock解锁,代码如下所示:

public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock();
    ExecutorService testSynchronizedBlock = Executors.newCachedThreadPool();
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" enter lock first");
            reentrantLock.lock();
            System.out.println(Thread.currentThread().getName()+" enter lock");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+" enter lock again");
            reentrantLock.lock();
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+" exit lock again");
            reentrantLock.unlock();
            System.out.println(Thread.currentThread().getName()+" exit lock");
            reentrantLock.unlock();
        }
    });
    testSynchronizedBlock.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" enter lock before");
            reentrantLock.lock();
            System.out.println(Thread.currentThread().getName()+" enter lock");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+" exit lock");
            reentrantLock.unlock();
        }
    });
}

上述代码,在第一个代码中演示了ReentrantLock的可重入性,在使用并发锁相关工具类时,一定要注意获取锁和释放锁必须一一配对,在任何情况下都要确保能释放锁,以免造成死锁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/461455.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PHP-8.2.5+IIS10 php-cgi.exe - FastCGI 进程意外退出

服务器信息&#xff1a; Windows Server 2019 Standard. Internet Information Services(Version 10.0.17763.1) PHP版本&#xff1a; PHP Version 8.2.5 php-8.2.5-nts-Win32-vs16-x64 下载地址&#xff1a;https://windows.php.net/download#php-8.2 错误信息&#xff1a; H…

【网络】-- UDP协议

目录 传输层 再谈端口号 端口号范围划分 认识知名端口号&#xff08;Well-Know Port Number&#xff09; 两个问题 netstat pidof UDP协议 UDP的特点 UDP的缓冲区 UDP使用注意事项 基于UDP的应用层协议 传输层 负责数据能够从发送端传输接收端。 再谈端口号 端…

【ROS】ubuntu18.04安装ROS(ROS1 Melodic)

1、添加中科大ROS源 1.1、添加源 sudo sh -c . /etc/lsb-release && echo "deb http://mirrors.ustc.edu.cn/ros/ubuntu/ lsb_release -cs main" > /etc/apt/sources.list.d/ros-latest.list1. 2、添加公钥 sudo apt-key adv --keyserver hkp://keyser…

输入捕获实验

实验内容 用TIM5 的通道 1&#xff08;PA0&#xff09;来做输入捕获&#xff0c;捕获 PA0 上高电平的脉宽&#xff08;用 WK_UP 按键输入高电平&#xff09;&#xff0c;通过串口打印高电平脉宽时间。 输入捕获简介 输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32 的…

新库上线 | 全国工艺美术大师信息数据

全国工艺美术大师信息数据 一、数据简介 作为物质产品&#xff0c;工艺美术反映着一定时代、一定社会的物质的和文化的生产水平&#xff1b;作为精神产品&#xff0c;它的视觉形象&#xff08;造型、色彩、装饰&#xff09;又体现了一定时代的审美观。我国工艺美术品的制作较早…

java基础+注解笔记【狂神说java】

基础部分–总结 基础包的命名 //一般为域名倒置 page com.yang.base基础语法 类和方法 根据不同的写法–大小写的差异可以判别是类对象还是方法名 类的首字母都要大写&#xff0c;方法首字母小写可以理解为&#xff0c;类对象是class&#xff0c;方法是函数 类对象 方法 使…

浅谈在 Vue2 和 Vue3 中计算属性和侦听器的一些变化

文章目录 &#x1f4cb;前言&#x1f3af;计算属性&#x1f3af;侦听器&#x1f4dd;最后 &#x1f4cb;前言 计算属性 computed 和侦听器 watch 都是 Vue.js 框架中用来响应式更新视图的重要概念。因此无论是在哪个版本&#xff0c;它们都是不可缺少的概念&#xff0c;这篇文…

音视频八股文(5)--SDL音视频渲染实战。会使用就行,不需要深究。

01-SDL子系统 SDL将功能分成下列数个子系统&#xff08;subsystem&#xff09;&#xff1a; ◼ SDL_INIT_TIMER&#xff1a;定时器 ◼ SDL_INIT_AUDIO&#xff1a;音频 ◼ SDL_INIT_VIDEO&#xff1a;视频 ◼ SDL_INIT_JOYSTICK&#xff1a;摇杆 ◼ SDL_INIT_HAPTIC&#xff1…

第十三章 享元模式

文章目录 前言一、享元模式基本介绍二、享元模式解决网站展现项目完整代码WebSite 抽象网站类User 外部状态用户内部状态网站 ConcreteWebSite网站工厂产生网站和负责共享&#xff08;池&#xff09; WebSiteFactoryClint 测试 三、享元模式在JDK-Interger的应用源码分析四、享…

ERBuilder Data Modeler Crack

ERBuilder Data Modeler Crack 为过程、视图和触发器添加了人工智能驱动的描述生成。 添加了一种新的自动排列方法&#xff0c;可以轻松地排列和组织表格&#xff0c;从而简化ER图的可视化显示。 添加了使用两种身份验证方法创建到远程服务器的SSH连接的功能&#xff1a;密码身…

Linux - 第10节 - Linux多线程(二)

1.Linux线程同步 1.1.同步概念与竞态条件 线程互斥的设计是正确的&#xff0c;但线程互斥在某些场景下并不合理&#xff0c;有可能导致饥饿问题。 饥饿问题&#xff1a;某个执行流访问完临界资源后释放锁&#xff0c;此时相较于其他执行流&#xff0c;该执行流离锁更近&#x…

【目标检测论文阅读笔记】Detection of plane in remote sensing images using super-resolution

Abstract 由于大量的小目标、实例级噪声和云遮挡等因素&#xff0c;遥感图像的目标检测精度低&#xff0c;漏检率或误检率高。本文提出了一种新的基于SRGAN和YOLOV3的目标检测模型&#xff0c;称为SR-YOLO。解决了SRGAN网络 对超参数的敏感性和模态崩溃问题。同时&#xff0c;Y…

【中级软件设计师】—(针对上午题)算法分析与设计(三十八)

【中级软件设计师】—&#xff08;针对上午题&#xff09;算法分析与设计&#xff08;三十八&#xff09; 一、回溯法 1. 什么是回溯法&#xff1f; 相信"迷宫"是许多人儿时的回忆&#xff0c;大家小时候一定都玩过迷宫游戏。我们从不用别人教&#xff0c;都知道走…

TryHackMe-M4tr1x: Exit Denied(boot2root)

M4tr1x: Exit Denied 大多数人只看到一个完美构建的系统。但你一直都是不同的。你不仅看到表面上的东西&#xff0c;还看到 它下面有什么统治;调节和调节的内部关联机制 几乎完美地管理其每个模块&#xff0c;以至于它试图隐藏所有模块 其多面设计中的微小孔。但是&#xff0c…

【数据结构】链表详解

本片要分享的内容是链表&#xff0c;为方便阅读以下为本片目录 目录 1.顺序表的问题及思考 1.链表的遍历 2.头部插入 2.1开辟空间函数分装 3.尾部插入 纠正 4.尾部删除 5.头部删除 6.数据查找 7.任意位置插入 1.顺序表的问题及思考 上一篇中讲解了顺序表中增删查…

【Linux】如何理解缓冲区

文章目录 &#x1f4d5; 看现象&#x1f4d5; 理解本质&#x1f4d5; 模拟文件接口mystdio.hmystdio.c &#x1f4d5; 看现象 如下代码&#xff0c;运行结果如图。 1 #include<sys/types.h> 2 #include<sys/stat.h> 3 #include<fcntl.h> 4 #include<s…

算法强化--分解因数

大家好,今天为大家带来一道题目 链接&#xff1a;https://www.nowcoder.com/questionTerminal/0f6976af36324f8bab1ea61e9e826ef5 来源&#xff1a;牛客网 [编程题]分解因数 热度指数&#xff1a;8605时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒空间限制&#xff1a;…

臻图信息:数字技术推动智慧楼宇开启新模式

近年来&#xff0c;在数字技术的迅速发展下&#xff0c;正在深刻影响着各行各业的发展趋势。现代建筑行业已经随着通信技术、AI 智能技术、计算机技术的发展&#xff0c;向着新的发展模式开始转变。 借助数字孪生技术构建数字化、流程化的物联网平台&#xff0c;新的智能楼宇建…

串口通讯详解

目录 一、串口通讯简介&#xff1a; 二、串口通信基本原理&#xff1a; 三、通信方式 四、串口通信特点 一、串口通讯简介&#xff1a; &#xff08;1&#xff09;串口通讯是指通过串口进行数据传输的一种通讯方式&#xff0c;通过数据信号线、地线等&#xff0c;按位进行传输数…

【Python】实战:生成无关联单选问卷 csv《社会参与评估表》

目录 一、适用场景 二、业务需求 三、Python 文件 &#xff08;1&#xff09;创建文件 &#xff08;2&#xff09;代码示例 四、csv 文件 一、适用场景 实战场景&#xff1a; 问卷全部为单选题问卷问题全部为必填问题之间无关联关系每个问题的答案分数不同根据问卷全…