Java并发编程(二)如何保证线程同时/交替执行
第一篇文章中,我用如何保证线程顺序执行的例子作为Java并发系列的开胃菜。本篇我们依然不会有源码分析,而是用另外两个多线程的例子来引出Java.util.concurrent中的几个并发工具的用法。
系列文章
Java并发编程(一)如何保证线程顺序执行 - 简书 (jianshu.com)
一、如何保证多个线程同时执行
保证多个线程同时执行,指的是多个线程在同一时间开始执行内部run()方法。
经过第一篇的学习,你应该能理解到,让线程能按我们的意志来运行其实是需要用一些手段(信号量、并发工具、线程池等)来实现的。常用的并发工具一般有CountDownLatch、CyclicBarrier、Semaphore,这些工具在多线程编程中必不可少。我们先看看如何用并发工具保证线程同时执行吧。
1. 使用CountDownLatch实现
关于CountDownLatch,count down的字面意思是倒数,latch是上锁的意思。所以CountDownLatch的意思就是倒数关门。我们看看JDK8 API中是如何解释的:
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
大概意思是,CountDownLatch是一种同步辅助工具,允许一个或多个线程等待一组在其他线程中执行的操作完成之后再执行。
public class SimultaneouslyExample {
static CountDownLatch countDownLatch=new CountDownLatch(3);
public static void foo(String name) {
System.out.println("线程名:"+name+",开始时间:"+System.nanoTime());
try {
countDownLatch.await();
//2.每次减一
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException{
Thread thread1 = new Thread(() -> foo("A"));
Thread thread2 = new Thread(() -> foo("B"));
Thread thread3 = new Thread(() -> foo("C"));
thread1.start();
thread2.start();
thread3.start();
Thread.sleep(300);
countDownLatch.countDown();
}
}
输出结果:
线程名:A,开始时间:449768159780400
线程名:C,开始时间:449768159785200
线程名:B,开始时间:449768159795300
看到输出结果,你可能会怀疑。明明A线程慢了4800纳秒啊,这不是同步的。其实大可不必觉得奇怪,纳秒级的时间即使是JVM也没办法那么精准的把控,不过根据我的测试。这里的同步实现逻辑能保证毫秒级的精确性。
2. 使用CyclicBarrier实现
另一种实现方式CyclicBarrier,根据字面意思我可以看到这个是一个可循环屏障。CyclicBarrier可以让一个或多个线程到达一个屏障点之后再开始运行。
话不多说,我们直接看看代码中如何写:
public class CyclicBarrierExample{
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
public static void foo(String name) {
System.out.println("线程名:"+name+",开始时间:"+System.currentTimeMillis());
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException{
Thread thread1 = new Thread(() -> foo("A"));
Thread thread2 = new Thread(() -> foo("B"));
Thread thread3 = new Thread(() -> foo("C"));
thread1.start();
thread2.start();
thread3.start();
Thread.sleep(300);
}
}
输出结果:
线程名:A,开始时间:1621232496385
线程名:B,开始时间:1621232496385
线程名:C,开始时间:1621232496385
二、如何保证多个线程交替执行
保证多个线程交替执行,指的是多个线程可以按照一定的次序开始执行内部run()方法。这里我们需要使用Semaphore并发工具来实现。如何你的大学课程学习过操作系统的话,那么你一定对信号量机制很熟悉
Semaphore(信号量):是一种计数器,用来保护一个或者多个共享资源的访问。如果线程要访问一个资源就必须先获得信号量。如果信号量内部计数器大于0,信号量减1,然后允许共享这个资源;否则,如果信号量的计数器等于0,信号量将会把线程置入休眠直至计数器大于0.当信号量使用完时,必须释放。
Semaphore的初始化需要传入一个整型参数,此参数标识该信号量可以占用的资源个数。例如我们有两个信号量A,B。A信号量可以允许两个线程占用,B信号量允许一个线程占用,那么初始化的时候Semaphore A = new Semaphore(2);
public class AlternateExample {
private static Semaphore s1 = new Semaphore(1);
private static Semaphore s2 = new Semaphore(1);
private static Semaphore s3 = new Semaphore(1);
static Semaphore[] signals = {s1, s2, s3};
public static void foo(int name) {
while (true) {
try {
signals[name - 1].acquire();
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程名:" + name);
signals[(name) % 3].release();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> foo(1));
Thread thread2 = new Thread(() -> foo(2));
Thread thread3 = new Thread(() -> foo(3));
//先占用1和2,此处我们要保证的顺序是3、1、2
s1.acquire();
s2.acquire();
thread1.start();
thread2.start();
thread3.start();
Thread.sleep(300);
}
}
三、总结
本篇我们用两个问题引出了3个并发工具CountDownLatch、CyclicBarrier、Semaphore的实际应用的例子。下一篇我们讲从源码角度详细分析下这三个工具的实现细节。
参考文章
【完整代码】使用Semaphore实现线程的交替执行打印 A1B2C3D4E5_学亮编程手记-CSDN博客
CountDownLatch详解 - 简书 (jianshu.com)
Java中多个线程交替循环执行 - 坐看云起时_雨宣 - 博客园 (cnblogs.com)
JAVA Semaphore详解 - 简单爱_wxg - 博客园 (cnblogs.com)
Java并发编程(二)如何保证线程同时/交替执行的更多相关文章
- java并发编程(四) 线程池 & 任务执行、终止源码分析
参考文档 线程池任务执行全过程:https://blog.csdn.net/wojiaolinaaa/article/details/51345789 线程池中断:https://www.cnblog ...
- Java并发编程二三事
Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...
- Java并发编程:如何创建线程?
Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...
- 【转】Java并发编程:如何创建线程?
一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过 ...
- 2、Java并发编程:如何创建线程
Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...
- Java并发编程 | 从进程、线程到并发问题实例解决
计划写几篇文章讲述下Java并发编程,帮助一些初学者成体系的理解并发编程并实际使用,而不只是碎片化的了解一些Synchronized.ReentrantLock等技术点.在讲述的过程中,也想融入一些相 ...
- Java 并发编程——Executor框架和线程池原理
Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...
- [Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ...
[Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ... 摘要 介绍 Java 并发包里的几个主要 ExecutorService . 正文 ...
- Java 并发编程——Executor框架和线程池原理
Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...
- 原创】Java并发编程系列2:线程概念与基础操作
[原创]Java并发编程系列2:线程概念与基础操作 伟大的理想只有经过忘我的斗争和牺牲才能胜利实现. 本篇为[Dali王的技术博客]Java并发编程系列第二篇,讲讲有关线程的那些事儿.主要内容是如下这 ...
随机推荐
- JVM笔记--如果你写JVM,最需要考虑的重要结构是什么?
开局一张图,前面已经从每一部分解析过JVM的内存结构了,现在按照顺序来分析: 整体上来看:类文件从类加载子系统,加载完成之后,主要存放在方法区(JRockit和H9没有方法区,这里指的是HotSpot ...
- Hznu_oj 2340 你敢一个人过桥吗?
Description 在漆黑的夜里,N位旅行者来到了一座狭窄而且没有护栏的桥边.如果不借助手电筒的话,大家是无论如何也不敢过桥去的.不幸的是,N个人一共只带了一只手电筒,而桥窄得只够让两个人同时过. ...
- Docker上安装Redis
Docker可以很方便的进行服务部署和管理,下面我们通过docker来搭建Redis的单机模式.Redis主从复制.Redis哨兵模式.Redis-Cluster模式 一.在Docker上安装单机版R ...
- 微信小程序实现搜索关键词高亮
目录 1,前言 2,思路 3,代码逻辑 1,前言 项目中碰到一个需求,搜索数据并且关键词要高亮显示,接到需求,马上开干.先上效果图.源码已经做成了小程序代码片段,放入了GitHub了,文章底部有源码链 ...
- Python | random 模块:Python 中如何生成随机数和随机抽样?
random 是平时开发过程中常用的一个模块,该模块实现了各种分布的伪随机数生成器,以及和随机数相关的各种实用函数.基本函数 random() 在区间 [0.0, 1.0) 内均匀生成随机浮点数,是模 ...
- Linux 软链接link/ln -s
在Linux中,链接分为软的和硬的,至于两者之间有什么差别,大家可以参考下https://www.ibm.com/developerworks/cn/linux/l-cn-hardandsymb-li ...
- Ceph 14.2.5-K8S 使用Ceph存储实战 -- <6>
K8S 使用Ceph存储 PV.PVC概述 管理存储是管理计算的一个明显问题.PersistentVolume子系统为用户和管理员提供了一个API,用于抽象如何根据消费方式提供存储的详细信息.于是引入 ...
- 从HotSpot VM源码看字符串常量池(StringTable)和intern()方法
引言 字符串常量池(StringTable)是JVM中一个重要的结构,它有助于避免重复创建相同内容的String对象.那么StringTable是怎么实现的?"把字符串加入到字符串常量池中& ...
- Kubernetes部署metrics-server提示健康检测报错500,简单解决方式
为什么写? 最近有项目要用到HPA(Horizontal Pod Autoscaler)依赖了k8s的 metrics 指标才能做出自动缩扩容的动作,我这边用官方GitHub v0.4.2版本启动不起 ...
- SpringBoot+Gradle构建多模块项目
1 概述 Gradle由于构建速度比Maven快,且比Maven灵活,因此很多后端的应用都使用了Gradle进行构建,但一个问题是,Gradle的多模块项目比较难构建,再加上Gradle的更新非常快, ...