【Java进阶】Java并发包提供了哪些并发工具类?

article/2025/10/1 9:25:55

通过前面的学习,我们一起回顾了线程、锁等各种并发编程的基本元素,也逐步涉及了 Java 并发包中的部分内容,相信经过前面的热身,我们能够更快地理解 Java 并发包。

今天我要问你的问题是,Java 并发包提供了哪些并发工具类?

典型回答

我们通常所说的并发包也就是 java.util.concurrent 及其子包,集中了 Java 并发的各种基础工具类,具体主要包括几个方面:

  • 提供了比 synchronized 更加高级的各种同步结构,包括 CountDownLatch、CyclicBarrier、Semaphore 等,可以实现更加丰富的多线程操作,比如利用 Semaphore 作为资源控制器,限制同时进行工作的线程数量。
  • 各种线程安全的容器,比如最常见的 ConcurrentHashMap、有序的 ConcunrrentSkipListMap,或者通过类似快照机制,实现线程安全的动态数组 CopyOnWriteArrayList 等。
  • 各种并发队列实现,如各种 BlockedQueue 实现,比较典型的 ArrayBlockingQueue、SynchorousQueue 或针对特定场景的 PriorityBlockingQueue 等。
  • 强大的 Executor 框架,可以创建各种不同类型的线程池,调度任务运行等,绝大部分情况下,不再需要自己从头实现线程池和任务调度器。

考点分析

这个题目主要考察你对并发包了解程度,以及是否有实际使用经验。我们进行多线程编程,无非是达到几个目的:

  • 利用多线程提高程序的扩展能力,以达到业务对吞吐量的要求。
  • 协调线程间调度、交互,以完成业务逻辑。
  • 线程间传递数据和状态,这同样是实现业务逻辑的需要。

所以,这道题目只能算作简单的开始,往往面试官还会进一步考察如何利用并发包实现某个特定的用例,分析实现的优缺点等。

如果你在这方面的基础比较薄弱,我的建议是:

  • 从总体上,把握住几个主要组成部分(前面回答中已经简要介绍)。
  • 理解具体设计、实现和能力。
  • 再深入掌握一些比较典型工具类的适用场景、用法甚至是原理,并熟练写出典型的代码用例。

掌握这些通常就够用了,毕竟并发包提供了方方面面的工具,其实很少有机会能在应用中全面使用过,扎实地掌握核心功能就非常不错了。真正特别深入的经验,还是得靠在实际场景中踩坑来获得。

知识扩展

首先,我们来看看并发包提供的丰富同步结构。前面几讲已经分析过各种不同的显式锁,今天我将专注于

  • CountDownLatch,允许一个或多个线程等待某些操作完成。
  • CyclicBarrier,一种辅助性的同步结构,允许多个线程等待到达某个屏障。
  • Semaphore,Java 版本的信号量实现。

Java 提供了经典信号量(Semaphore)的实现,它通过控制一定数量的允许(permit)的方式,来达到限制通用资源访问的目的。你可以想象一下这个场景,在车站、机场等出租车时,当很多空出租车就位时,为防止过度拥挤,调度员指挥排队等待坐车的队伍一次进来 5个人上车,等这 5 个人坐车出发,再放进去下一批,这和 Semaphore 的工作原理有些类似。

你可以试试使用 Semaphore 来模拟实现这个调度过程:

import java.util.concurrent.Semaphore;
public class UsualSemaphoreSample {public static void main(String[] args) throws InterruptedException {System.out.println("Action...GO!");Semaphore semaphore = new Semaphore(5);for (int i = 0; i < 10; i++) {Thread t = new Thread(new SemaphoreWorker(semaphore));t.start();}}
}
class SemaphoreWorker implements Runnable {private String name;private Semaphore semaphore;public SemaphoreWorker(Semaphore semaphore) {this.semaphore = semaphore;}@Overridepublic void run() {try {log("is waiting for a permit!");semaphore.acquire();log("acquired a permit!");log("executed!");} catch (InterruptedException e) {e.printStackTrace();} finally {log("released a permit!");semaphore.release();}}private void log(String msg){if (name == null) {name = Thread.currentThread().getName();}System.out.println(name + " " + msg);}
}

这段代码是比较典型的 Semaphore 示例,其逻辑是,线程试图获得工作允许,得到许可则进行任务,然后释放许可,这时等待许可的其他线程,就可获得许可进入工作状态,直到全部处理结束。编译运行,我们就能看到 Semaphore 的允许机制对工作线程的限制。

但是,从具体节奏来看,其实并不符合我们前面场景的需求,因为本例中 Semaphore 的用法实际是保证,一直有 5 个人可以试图乘车,如果有 1 个人出发了,立即就有排队的人获得许可,而这并不完全符合我们前面的要求。

那么,我再修改一下,演示个非典型的 Semaphore 用法。

import java.util.concurrent.Semaphore;
public class AbnormalSemaphoreSample {
public static void main(String[] args) throws InterruptedException {Semaphore semaphore = new Semaphore(0);for (int i = 0; i < 10; i++) {Thread t = new Thread(new MyWorker(semaphore));t.start();}System.out.println("Action...GO!");semaphore.release(5);System.out.println("Wait for permits off");while (semaphore.availablePermits()!=0) {Thread.sleep(100L);}System.out.println("Action...GO again!");semaphore.release(5);}
}
class MyWorker implements Runnable {private Semaphore semaphore;public MyWorker(Semaphore semaphore) {this.semaphore = semaphore;}@Overridepublic void run() {try {semaphore.acquire();System.out.println("Executed!");} catch (InterruptedException e) {e.printStackTrace();}}
}

注意,上面的代码,更侧重的是演示 Semaphore 的功能以及局限性,其实有很多线程编程中的反实践,比如使用了 sleep 来协调任务执行,而且使用轮询调用 availalePermits 来检测信号量获取情况,这都是很低效并且脆弱的,通常只是用在测试或者诊断场景。

总的来说,我们可以看出 Semaphore 就是个计数器,其基本逻辑基于acquire/release,并没有太复杂的同步逻辑。

如果 Semaphore 的数值被初始化为 1,那么一个线程就可以通过 acquire 进入互斥状态,本质上和互斥锁是非常相似的。但是区别也非常明显,比如互斥锁是有持有者的,而对于 Semaphore 这种计数器结构,虽然有类似功能,但其实不存在真正意义的持有者,除非我们进行扩展包装。

下面,来看看 CountDownLatch 和 CyclicBarrier,它们的行为有一定的相似度,经常会被考察二者有什么区别,我来简单总结一下。

  • CountDownLatch 是不可以重置的,所以无法重用;而 CyclicBarrier 则没有这种限制,可以重用。
  • CountDownLatch 的基本操作组合是 countDown/await。调用 await 的线程阻塞等待 countDown足够的次数,不管你是在一个线程还是多个线程里 countDown,只要次数足够即可。所以就像 Brain Goetz 说过的,CountDownLatch 操作的是事件。
  • CyclicBarrier 的基本操作组合,则就是 await,当所有的伙伴(parties)都调用了await,才会继续进行任务,并自动进行重置。注意,正常情况下,CyclicBarrier 的重置 都是自动发生的,如果我们调用 reset 方法,但还有线程在等待,就会导致等待线程被打扰,抛出BrokenBarrierException 异常。CyclicBarrier 侧重点是线程,而不是调用事 件,它的典型应用场景是用来等待并发线程结束。

如果用 CountDownLatch 去实现上面的排队场景,该怎么做呢?假设有 10 个人排队,我们将其分成 5 个人一批,通过 CountDownLatch 来协调批次,你可以试试下面的示例代码。

import java.util.concurrent.CountDownLatch;
public class LatchSample {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(6);for (int i = 0; i < 5; i++) {Thread t = new Thread(new FirstBatchWorker(latch));t.start();}for (int i = 0; i < 5; i++) {Thread t = new Thread(new SecondBatchWorker(latch));t.start();}// 注意这里也是演示目的的逻辑,并不是推荐的协调方式while ( latch.getCount() != 1 ){Thread.sleep(100L);}System.out.println("Wait for first batch finish");latch.countDown();}
}
class FirstBatchWorker implements Runnable {private CountDownLatch latch;public FirstBatchWorker(CountDownLatch latch) {this.latch = latch;}@Overridepublic void run() {System.out.println("First batch executed!");latch.countDown();}
}
class SecondBatchWorker implements Runnable {private CountDownLatch latch;public SecondBatchWorker(CountDownLatch latch) {this.latch = latch;}@Overridepublic void run() {try {latch.await();System.out.println("Second batch executed!");} catch (InterruptedException e) {e.printStackTrace();}}
}

CountDownLatch 的调度方式相对简单,后一批次的线程进行 await,等待前一批countDown 足够多次。这个例子也从侧面体现出了它的局限性,虽然它也能够支持 10 个人排队的情况,但是因为不能重用,如果要支持更多人排队,就不能依赖一个CountDownLatch 进行了。其编译运行输出如下:
在这里插入图片描述
在实际应用中的条件依赖,往往没有这么别扭,CountDownLatch 用于线程间等待操作结束是非常简单普遍的用法。通过 countDown/await 组合进行通信是很高效的,通常不建议使用例子里那个循环等待方式。

如果用 CyclicBarrier 来表达这个场景呢?我们知道 CyclicBarrier 其实反映的是线程并行运行时的协调,在下面的示例里,从逻辑上,5 个工作线程其实更像是代表了 5 个可以就绪的空车,而不再是 5 个乘客,对比前面 CountDownLatch 的例子更有助于我们区别它们的抽象模型,请看下面的示例代码:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierSample {public static void main(String[] args) throws InterruptedException {CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {@Overridepublic void run() {System.out.println("Action...GO again!");}});for (int i = 0; i < 5; i++) {Thread t = new Thread(new CyclicWorker(barrier));t.start();}}static class CyclicWorker implements Runnable {private CyclicBarrier barrier;public CyclicWorker(CyclicBarrier barrier) {this.barrier = barrier;}@Overridepublic void run() {try {for (int i=0; i<3 ; i++){System.out.println("Executed!");barrier.await();}} catch (BrokenBarrierException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}}
}

为了让输出更能表达运行时序,我使用了 CyclicBarrier 特有的 barrierAction,当屏障被触发时,Java 会自动调度该动作。因为 CyclicBarrier 会自动进行重置,所以这个逻辑其实可以非常自然的支持更多排队人数。其编译输出如下:
在这里插入图片描述
Java 并发类库还提供了Phaser,功能与 CountDownLatch 很接近,但是它允许线程动态地注册到 Phaser 上面,而 CountDownLatch 显然是不能动态设置的。Phaser 的设计初衷是,实现多个线程类似步骤、阶段场景的协调,线程注册等待屏障条件触发,进而协调彼此间行动,具体请参考这个例子。

接下来,我来梳理下并发包里提供的线程安全 Map、List 和 Set。首先,请参考下面的类图。
在这里插入图片描述
你可以看到,总体上种类和结构还是比较简单的,如果我们的应用侧重于 Map 放入或者获取的速度,而不在乎顺序,大多推荐使用 ConcurrentHashMap,反之则使用ConcurrentSkipListMap;如果我们需要对大量数据进行非常频繁地修改,ConcurrentSkipListMap 也可能表现出优势。

我在前面的专栏,谈到了普通无顺序场景选择 HashMap,有顺序场景则可以选择类似TreeMap 等,但是为什么并发容器里面没有 ConcurrentTreeMap 呢?

这是因为 TreeMap 要实现高效的线程安全是非常困难的,它的实现基于复杂的红黑树。为保证访问效率,当我们插入或删除节点时,会移动节点进行平衡操作,这导致在并发场景中难以进行合理粒度的同步。而 SkipList 结构则要相对简单很多,通过层次结构提高访问速度,虽然不够紧凑,空间使用有一定提高(O(nlogn)),但是在增删元素时线程安全的开销要好很多。为了方便你理解 SkipList 的内部结构,我画了一个示意图。
在这里插入图片描述
关于两个 CopyOnWrite 容器,其实 CopyOnWriteArraySet 是通过包装了CopyOnWriteArrayList 来实现的,所以在学习时,我们可以专注于理解一种。

首先,CopyOnWrite 到底是什么意思呢?它的原理是,任何修改操作,如 add、set、remove,都会拷贝原数组,修改后替换原来的数组,通过这种防御性的方式,实现另类的线程安全。请看下面的代码片段,我进行注释的地方,可以清晰地理解其逻辑。

public boolean add(E e) {synchronized (lock) {Object[] elements = getArray();int len = elements.length;// 拷贝Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;// 替换setArray(newElements);return true;}
}
final void setArray(Object[] a) {array = a;
}

所以这种数据结构,相对比较适合读多写少的操作,不然修改的开销还是非常明显的。

今天我对 Java 并发包进行了总结,并且结合实例分析了各种同步结构和部分线程安全容器,希望对你有所帮助。


http://chatgpt.dhexx.cn/article/sATHTppV.shtml

相关文章

java---JUC并发包详解

目录 前言 一、atomic包 AtomicInteger类 AtomicReference类 AtomicStampedReference类 二、locks包 接口 Condition Lock ReadWriteLock 实现类 ReentrantLock类 ReentrantReadWriteLock类 三、CountDownLatch 四、Semaphore(信号量) 总结 前言 JUC是java.u…

Java多线程并发编程--Java并发包(JUC)

Java多线程并发–Java并发包&#xff08;JUC&#xff09; 前言 前一篇文章中&#xff0c;笔者已经介绍了Java多线程的一些基础知识&#xff0c;但是想要成为一名中高级Java程序员还必须懂得Java并发包&#xff08;JUC&#xff09;的知识点&#xff0c;而且JUC现在也是面试中必…

接口测试--apipost如何自定义header中的content-type

使用apipost进行接口测试的时候&#xff0c;有时候会用到一些自定义或者不常见的content-type格式&#xff0c;这个时候就要手动在header头部自定义content-type。 这里我们自定义一个content-type&#xff0c;格式为application/octet-stream 然后body选择的为form-data&…

Type-c引脚定义

Type-c口是什么口&#xff0c;有什么作用&#xff1f; Type-c口在大家的视野中或许比较陌生&#xff0c;但是生活中处处离不开Type-c口的存在。手机&#xff0c;电脑&#xff0c;音箱&#xff0c;小家电&#xff0c;无人机…等等&#xff0c;都存在Type-c接口。 Type-c只是一种…

TYPE-C12PIN接口电路

16PIN封装只有12个PIN脚&#xff0c;所有16/12PIN其实是一种规格 特点&#xff0c;正反插 VBS-VCC A5,B5接电阻拉地 A7,B7串联接电阻成为DN A6,B6串联接电阻成为DP GND拉地 外壳拉地 USB接口介绍

TypeScript-interface接口篇

简介 接口的作用&#xff1a;在面向对象的编程中&#xff0c;接口是一种规范的定义&#xff0c;它定义了行为和动作的规范&#xff0c;在程序设计里面&#xff0c;接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范&#xff0c;接口不关心这些类的内部状态数…

TypeScript(四)接口

目录 前言 定义 用法 基本用法 约定规则 属性控制 任意属性 可选属性 只读属性 定义函数 冒号定义 箭头定义 接口类型 函数接口 索引接口 继承接口 类接口 总结 前言 本文收录于TypeScript知识总结系列文章&#xff0c;欢迎指正&#xff01; 在介绍TS对象…

Type-C接口简单介绍-面向单片机应用

Type-C接口简单介绍-面向单片机应用 1、绪论 用单片机做一些东西时&#xff0c;Type-C接口逐渐替代了MicroUSB接口。但不像MicroUSB那样只有5V、GND、D、D-、ID五个接口&#xff0c;Type-C接口有24个引脚&#xff0c;比较复杂。大多时候我们用TypeC也用不到USB3.0协议&#x…

CTP_将C++封装为Python可调用接口

目录 写在前面&#xff1a; 前置准备&#xff1a; step 1 与上期所原始代码对比分析源码 td源码 1 配置属性-》常规-》配置类型 要为 “动态库(.dll)” 2 VC目录 -》包含目录 3 VC目录 -》 库目录 4 链接器-》常规-》附加库目录 5 链接器-》输入-》附加依赖项 vnctp.h 的功…

一文读懂USB Type-C接口 <一>:引脚和功能指南

本文将介绍USB Type-C标准的一些最重要的特性。 你知道如何使用USB Type-C接口吗?本文列出了USB Type-C引脚的解剖结构&#xff0c;并简要介绍了其各种模式。 USB Type-C是一种USB连接器系统的规范&#xff0c;它在智能手机和移动设备中越来越受欢迎&#xff0c;能够提供电力和…

VisionPro连接相机步骤

一、修改相机与电脑IP地址在同一网段上 1、修改相机IP地址 在菜单栏找到 “Cognex GigE Vision Configurator” &#xff0c;可直接输出搜索。 或者在visionPro默认安装目录下 “C:\Program Files\Cognex\VisionPro\bin”&#xff0c;找到“Cognex GigE Vision Configurator…

机器视觉——相机选型

目录 相机选型 分辨率、快门、帧率、色彩、靶面、接口 镜头选型 分辨率、靶面、焦距、接口、光圈畸变工作距离 常用计算示例 相机选型 分辨率、快门、帧率、色彩、靶面、接口 镜头选型 分辨率、靶面、焦距、接口、光圈畸变工作距离 常用计算示例 1. 面阵相机和镜头选型 已…

线扫相机的选择

1.通过幅宽和精度求像素个数选择相机&#xff1a; Rmin为最小像素数&#xff1b;FOV为检测幅宽&#xff1b;X为检测精度。通过计算结果选择相机大小。 如果幅宽要求120mm&#xff0c;精度要求0.1&#xff0c;得到最少所需像素个数为1200&#xff0c;选择2k的线扫相机即可满足。…

1-CCD相机选型

1-相机分类 2-以像素数选择&#xff08;高像素型或标准型&#xff09; 从“像素分辨率”这一点来添加良否判定的基准&#xff0c;可选择最佳像素数的相机&#xff01; 视觉系统所使用的 CCD 拍摄元件是以格子状排列的较小像素的集合体。在作为标准型经常使用的 31 万像素 CCD …

相机位姿估计

相机位姿估计 前言旋转角度欧拉角相机位姿求解旋转矩阵和旋转向量之间的转换旋转矩阵和欧拉角之间的转换平移量求解代码 前言 这部分内容博主也不是很熟悉&#xff0c;写下这篇博文想记录下自己当时的求解过程&#xff0c;也想让看到的朋友一起讨论&#xff0c;看看我做的对不…

iOS 相册多选 相机选择图片

前言 经过几天的断断续续的编写终于把这一个小项目完成了&#xff0c;现在刚刚完成&#xff0c;代码看着不整洁&#xff0c;请多包涵。 前几天要弄个相册多选和照相选图的功能&#xff0c;以前做过单选上传头像之类的。但是多选确实不像那么简单&#xff0c;github找了好多的…

工业相机和镜头选型技巧

工业相机和镜头选型技巧 一、加接圈&#xff0c;视野为什么会变小&#xff1f;1、视野公式理解2、加接圈后视野变小分析 二、在如下试验台中&#xff0c;加了接圈&#xff0c;图像要清晰&#xff0c;那么相机高度应该如何调整&#xff1f;1、试验台场景2、像距、物距和焦距的关…

工业视觉检测如何选择合适的工业相机?

1、根据应用的不同分别选用CCD或CMOS相机CCD工业相机主要应用在运动物体的图像提取&#xff0c;如贴片机机器视觉&#xff0c;当然随着CMOS技术的发展&#xff0c;许多贴片机也在选用CMOS工业相机。用在视觉自动检查的方案或行业中一般用CCD工业相机比较多。CMOS工业相机由成本…

【工业相机】【深度3】相机选择-精度和曝光需求计算 - 输入:1 被测试物体的最小体积 2 被测物体的移动相对速度

前言&#xff1a;本举例&#xff0c;说明&#xff0c;我们在工业场景下&#xff0c;如果需要在某个速度下计算某个尺寸的物体的工业相机的精度计算方法 1 需求定义 本需求定义为测量一个有移动速度的工业被测物体&#xff1a; 输入参数标识输入参数举例FOVFOV12寸&#xff08…