第一篇文章中,我用如何保证线程顺序执行的例子作为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个并发工具CountDownLatchCyclicBarrierSemaphore的实际应用的例子。下一篇我们讲从源码角度详细分析下这三个工具的实现细节。

参考文章

【完整代码】使用Semaphore实现线程的交替执行打印 A1B2C3D4E5_学亮编程手记-CSDN博客

CountDownLatch详解 - 简书 (jianshu.com)

Java中多个线程交替循环执行 - 坐看云起时_雨宣 - 博客园 (cnblogs.com)

JAVA Semaphore详解 - 简单爱_wxg - 博客园 (cnblogs.com)

Java并发编程(二)如何保证线程同时/交替执行的更多相关文章

  1. java并发编程(四) 线程池 & 任务执行、终止源码分析

    参考文档 线程池任务执行全过程:https://blog.csdn.net/wojiaolinaaa/article/details/51345789 线程池中断:https://www.cnblog ...

  2. Java并发编程二三事

    Java并发编程二三事 转自我的Github 近日重新翻了一下<Java Concurrency in Practice>故以此文记之. 我觉得Java的并发可以从下面三个点去理解: * ...

  3. Java并发编程:如何创建线程?

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  4. 【转】Java并发编程:如何创建线程?

    一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过 ...

  5. 2、Java并发编程:如何创建线程

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  6. Java并发编程 | 从进程、线程到并发问题实例解决

    计划写几篇文章讲述下Java并发编程,帮助一些初学者成体系的理解并发编程并实际使用,而不只是碎片化的了解一些Synchronized.ReentrantLock等技术点.在讲述的过程中,也想融入一些相 ...

  7. Java 并发编程——Executor框架和线程池原理

    Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...

  8. [Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ...

    [Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ... 摘要 介绍 Java 并发包里的几个主要 ExecutorService . 正文 ...

  9. Java 并发编程——Executor框架和线程池原理

    Java 并发编程系列文章 Java 并发基础——线程安全性 Java 并发编程——Callable+Future+FutureTask java 并发编程——Thread 源码重新学习 java并发 ...

  10. 原创】Java并发编程系列2:线程概念与基础操作

    [原创]Java并发编程系列2:线程概念与基础操作 伟大的理想只有经过忘我的斗争和牺牲才能胜利实现. 本篇为[Dali王的技术博客]Java并发编程系列第二篇,讲讲有关线程的那些事儿.主要内容是如下这 ...

随机推荐

  1. 解决unbutu网络编程socket_tcp连接不上网络助手

    unbutu开放指定端口 开放端口8080 sudo iptables -I INPUT -p tcp --dport 8080 -j ACCEPT 保存设置 iptables-save 在终端中输入 ...

  2. 攻防世界 reverse 流浪者

    流浪者 int __thiscall sub_401890(CWnd *this) { struct CString *v1; // ST08_4 CWnd *v2; // eax int v3; / ...

  3. c++ 反汇编 构造函数和析构函数

    构造函数和析构函数出现的时机 局部对象 109: // 局部对象定义调用构造函数 110: 111: CNumber Number; 00C8A37D 8D 4D EC lea ecx,[Number ...

  4. Java学习之数组的简单用法

    •概念 其实所谓的数组指的就是一组相关类型的变量集合,并且这些变量可以按照统一的方式进行操作. 数组本身属于引用数据类型,那么既然是引用数据类型,这里面实际又会牵扯到内存分配: 而数组的定义语法有两种 ...

  5. kong更改日志格式

    基于业务的需求,需要对网关层的日志进行适当定制,以满足使用kibana的制图. 对于kong的日志格式更改,可查看到的资料都过于繁琐,特此记录. 修改kong的日志格式 # ctl edit depl ...

  6. for what? while 与 until 差在哪?-- Shell十三问<第十三问>

    for what? while 与 until 差在哪?-- Shell十三问<第十三问> 最后要介绍的是 shell script 设计中常见的"循环"(loop). ...

  7. rancher的ssl部署

    前言 因为我司有多套k8s环境,管理起来过于麻烦,需要一个统一的管理平台,又因为没有预留时间自己开发,经过选择后,使用rancher来进行多k8s环境的统一管理平台. 部署 1.在阿里云上申请免费的证 ...

  8. Vue 中的 mixin,component,render,hoc

    在项目中,一般我们经常会基于一套现有组件库进行快速开发,但是现实中往往需要对组件库进行定制化改造二次封装 混入(mixin) vue 官方介绍 混入 (mixin) 提供了一种非常灵活的方式,来分发 ...

  9. Spring Boot 实现配置文件加解密原理

    Spring Boot 配置文件加解密原理就这么简单 背景 接上文<失踪人口回归,mybatis-plus 3.3.2 发布>[1] ,提供了一个非常实用的功能 「数据安全保护」 功能,不 ...

  10. Oracle-DG 主库将log_archive_dest_state_2远程归档线程参数设置为defer,为什么dg还是处于实时同步状态?

    一.需求,前段时间,墨天伦有个小伙伴咨询了这个问题,搞了测试环境测试下. Oracle-DG 主库将log_archive_dest_state_2远程归档线程参数设置为defer,为什么dg还是处于 ...