当前位置: 首页 > news >正文

Java 并发编程(七)|JUC 工具类与线程安全集合考点

并发

  • 前言
  • JUC工具类
    • Callable 接口
      • 示例
      • 小结
    • ReentrantLock 可重入锁
      • 使用
      • 优势
      • 劣势
    • 信号量 Semaphore
      • 示例
    • CountDownLatch
      • 示例
  • 线程安全的集合类
    • 多环境使用ArrayList
    • 多线程环境使用队列
    • 多线程环境使用哈希表
      • Hashtable
      • ConcurrentHashMap
  • 全文总结
  • 🔗 系列文章导航

前言

上一篇我们讲解了各类锁策略、CAS 无锁编程、synchronized 底层锁升级优化。本篇聚焦并发工程实战内容,完整梳理 JUC 包核心工具类与各类线程安全集合,覆盖 Callable、ReentrantLock、Semaphore、CountDownLatch 以及 ConcurrentHashMap 等高频面试考点,配套可运行案例,补齐 Java 并发编程业务落地相关知识点。

JUC工具类

JUC(java.util.concurrent):
这里的内容主要是一些多线程相关的组件。

Callable 接口

Callable 接口:带有泛型参数的接口,有返回值,一种创建线程的方式。

  1. 和 Runnable 对比:
    (1)Runnable.run():无返回值;
    (2)Callable.call():支持返回执行结果,可抛出异常。
  2. 注意:Callable 不能直接传入 Thread 构造方法,必须依靠FutureTask包装。

示例

实现使用Callable来创建线程计算1+2+3+…+1000。

importjava.util.concurrent.Callable;importjava.util.concurrent.ExecutionException;importjava.util.concurrent.FutureTask;publicclassMain{publicstaticvoidmain(String[]args)throwsExecutionException,InterruptedException{//定义任务Callable<Integer>callable=newCallable<Integer>(){@OverridepublicIntegercall()throwsException{intsum=0;for(inti=1;i<=1000;i++){sum+=i;}returnsum;}};//把任务放到线程中执行//callable不能直接传给Thread的构造方法,要借用中间的类包装一下//futureTask相当于号码牌,拿着号码牌取结果FutureTask<Integer>futureTask=newFutureTask<>(callable);Threadt=newThread(futureTask);t.start();//此处的get就能获取到callable里面返回的结果//线程是并发执行,执行到主线程的get时,t线程可能还没执行完,此时,get就会阻塞System.out.println(futureTask.get());}}

小结

线程的创建方式:

  1. 继承Thread类,重写run方法(可以创建单独的类,也可以使用匿名内部类)。
  2. 实现Runnable接口,重写run方法(可以创建单独的类,也可以使用匿名内部类)。
  3. 实现Callable接口,重写call方法(可以创建单独的类,也可以使用匿名内部类)。
  4. 使用lambda表达式。
  5. ThreadFactory,线程工厂。
  6. 线程池。

ReentrantLock 可重入锁

使用

ReentrantLocklock=newReentrantLock();lock.lock();//加锁try{//working}finally{lock.unlock();//一定不能忘记unlock的调用}

相对来说,synchronized是Java的内置锁,由JVM自动管理锁的获取与释放,无需手动调用解锁方法,因此不会出现因忘记解锁或异常导致锁无法释放的问题。

优势

  1. ReentrantLock 在加锁时有两种方式:lock,与synchronized类似,若遇到锁冲突,就会一直阻塞等待;trylock,尝试加锁,若没加上就放弃,给了更多的可操作空间。
  2. ReentrantLock 提供了公平锁的实现,默认情况下是非公平锁。
  3. ReentrantLock 提供了更强大的等待通知机制。搭配Condition类实现等待通知,可显示唤醒几个线程。

劣势

ReentrantLock 使用起来复杂,容易忘记解锁。
加锁时,首选synchronized(背后还有一系列的优化手段)。

信号量 Semaphore

信号量,就是一个计数器,描述了“可用资源”的个数。
信号量用法:

  1. 每次申请一个可用资源,计数器-1 ⇒ P操作(原子操作)acquire。
  2. 每次释放一个可用资源,计数器+1 ⇒ V操作(原子操作)release。

假设信号量初始值为10,已经进行了十次P操作之后,值为零,此时继续进行P操作,会阻塞等待。

锁本质上就是一种特殊的信号量,即可用资源为1的信号量,值只可能为1和0,也就是二元信号量。

操作系统提供了信号量实现,提供了API,JVM封装了这样的API,就可以在Java代码中使用。

示例

publicclassMain{publicstaticvoidmain(String[]args)throwsInterruptedException{Semaphoresemaphore=newSemaphore(4);semaphore.acquire();System.out.println("P 操作");semaphore.acquire();System.out.println("P 操作");semaphore.acquire();System.out.println("P 操作");semaphore.acquire();System.out.println("P 操作");semaphore.acquire();System.out.println("P 操作");//semaphore.release();}}

开发中如果遇到了需要申请资源的场景,就可以使用信号量来实现了。

CountDownLatch

CountDownLatch,适用于多个线程来完成一系列任务的时候,用来衡量任务的进度是否完成。
CountDownLatch 的方法:

  1. await,调用时会阻塞,就会等待其他线程完成任务,所有的线程都完成了任务之后,此时await才会返回,就会继续往下走。
  2. countDown,告诉countDownLatch,当前这一个子任务完成了。
  3. 如果 countDown 调用总次数小于初始化数值,await 会永久阻塞。

示例

publicclassMain{publicstaticvoidmain(String[]args)throwsInterruptedException{//十个选手参赛,await会在10次调用完countDown之后才能继续执行CountDownLatchcountDownLatch=newCountDownLatch(10);//如果循环次数少于10,await会一直处于阻塞状态for(inti=0;i<10;i++){intid=i;Threadt=newThread(()->{System.out.println("thread "+id);try{Thread.sleep(500);}catch(InterruptedExceptione){thrownewRuntimeException(e);}//通知,当前任务已经执行完毕countDownLatch.countDown();});t.start();}countDownLatch.await();System.out.println("所有任务都完成了");}}

线程安全的集合类

数据结构中大部分的集合类都是线程不安全的,早期Vector,Stack,HashTable线程安全(自带synchronized)。

那么,如何保证线程安全呢?
1.加锁。
2.标准库也提供了一些搭配的组件,保证线程安全。

多环境使用ArrayList

  1. 使用同步机制(synchronized或ReentrantLock)。
  2. Collections.synchronizedList(new ArrayList):会返回一个新的对象,这个新的对象相当于给ArrayList套了一层壳,这层壳就是在方法上直接使用synchronized。
  3. 使用CopyOnWriteArrayList:写时拷贝
    (1)比如两个线程使用同一个ArrayList,可能会读,可能会写;两个线程读,就直接读取;若某个线程需要进行修改,就把ArrayList复制出来一个副本,该线程的修改操作就是在修改这个副本,与此同时,另一个线程仍然可以读取数据,是在原来的数据上读取,一旦这边修改完毕,就会使用修改好的副本替代掉原来的数据(往往就是一个引用赋值)。
    (2)当前操作的ArrayList不能太大,拷贝成本太高。
    (3)更适合于一个线程修改,多个线程读取的情况。
    (4)适合于服务器的配置更新;可以通过配置文件来描述配置的详细内容,配置的内容会被读到内存中,再由其他的线程读取这里的内容,但是修改这个配置内容,往往只有一个线程来修改。⇒ 使用某个命令让服务器重新加载配置,就可以使用写时拷贝的方式了。

多线程环境使用队列

  1. ArrayBlockingQueue:基于数组实现的阻塞队列。
  2. LinkedBlockingQueue:基于链表实现的阻塞队列。
  3. PriorityBlockingQueue:基于堆实现的带优先级的阻塞队列。
  4. TransferQueue:最多只包含一个元素的阻塞队列。

多线程环境使用哈希表

HashMap本身不是线程安全的

Hashtable

Hashtable 保证线程安全,主要就是给关键方法加上synchronized,直接加到方法上,相当于给this加锁。
只要两个线程在操作同一个Hashtable就会出现锁冲突。


1.在不考虑触发扩容的前提下,操作不同的链表是线程安全的,比如A和C。
2.相比之下,若两个线程操作的是同一个链表,才容易出现问题,比如A和B。
3.若两个线程操作的不是同一个链表,就不用加锁,否则,需要加锁。

ConcurrentHashMap

ConcurrentHashMap 最核心的改进:把一个全局的大锁,改进成了每个链表独立的一把小锁(把每个链表的头结点作为锁对象,synchronized可以使用任意对象作为锁对象),大幅度降低了锁冲突概率。
一个哈希表有很多这样的链表,两个线程恰好同时访问同一个链表的情况较少。

ConcurrentHashMap 充分利用了CAS特性,把一些不必要加锁的环节省略加锁了。
比如,使用变量记录hash表中的元素个数,就可以使用原子操作(CAS)修改元素个数

ConcurrentHashMap,针对读操作没有加锁。ConcurrentHashMap 在底层编码过程在=中,处理了一些细节,修改时会避免使用++、–这种非原子的操作,而是使用=进行修改,本身就是原子的,因此读的时候,要么读到写之前的旧值,要么读到写之后的新值,不会出现读到一个修改到一半的值。
写和写之间还是要加锁的。

ConcurrentHashMap针对扩容操作,做出了单独的优化。
本身HashMap和Hashtable在扩容时都需要把所有的元素都拷贝一遍,若元素很多,拷贝就比较耗时。
化整为零:一旦需要扩容,确实需要搬运,但不是在一次操作中搬运完成,而是分成多次来搬运,每次只搬运一部分数据,避免单次操作过于卡顿。

在Java8之前,ConcurrentHashMap 是基于分段锁(多个链表共用一把锁)的方式实现的,Java8之后就是直接在链表头结点加锁的形式。

全文总结

  1. Callable+FutureTask 实现带返回值线程;
  2. ReentrantLock、Semaphore、CountDownLatch 开发场景与底层特性;
  3. 各类线程安全 List、阻塞队列、哈希表对比,重点掌握 ConcurrentHashMap JDK8 优化细节。

至此 Java 并发编程系列全部知识点讲解完毕!整套专栏从基础线程、同步机制、锁、CAS、JUC 工具到并发容器完整闭环,全部代码均可直接运行。欢迎点赞收藏整套专栏,评论区交流面试问题!

🔗 系列文章导航

本篇是「Java并发编程系列」的连载内容,点击链接查看完整系列:

🔹 上一篇:Java 并发编程(六)|并发进阶高频:CAS、锁升级
🔹 下一篇:Java IO|File 文件操作 + 字节流 / 字符流完整笔记 + 递归删除文件实战
👉 点击直达「Java并发编程」专栏合集

http://www.jsqmd.com/news/1189181/

相关文章:

  • 深入解析Docker容器文件系统:从Overlay2驱动到宿主机目录映射
  • 2026年AI编程工具大变局:代码补全已成过去式,工程生成才是Java开发者的终极答案
  • ROS Action服务器中Goal Callback设计原理与最佳实践
  • OpenAI低延迟AI技术解析:WebSocket与硬件加速
  • 2026非现场执法治超系统十大品牌,广州聚杰品质靠谱信得过 - 品牌速递
  • 福州卖钻石先查资质!三类备案缺一不可,4 家正规福州钻石回收店铺资质真人核验测评 - 每日生活报
  • 【ChatGPT在线答疑辅助实战指南】:20年IT老兵亲授5大高危误用场景与实时纠错框架
  • 2026钻石回收怕被坑|查这三项备案一眼识别正规商家 - 奢侈品回收测评
  • 全新可逆性HER2抑制剂:Sevabertinib获批上市,填补NSCLC治疗空白
  • 华为MetaERP Oracle EBS vs SAP 期末结转(月结 + 年结)完整差异分析前置核心设计哲学总览SAP(德式一体化紧耦合)FI/CO/MM/AA 深度集成,账结法强制每月结转损
  • Go语言后端数据库表设计规范与最佳实践
  • 2026上海回收黄金注意避坑,总结5条行业内幕,卖黄金前认真看完 - 小蝶回收测评
  • Geek Uninstaller:告别卸载残留,用极简工具守护Windows系统纯净
  • 两地三中心建设方案曝光,云数据中心灾备必看指南!
  • PIC18F46K20与DTH-08模块的信号切换电路设计与优化
  • 【办公自动化工具】 OpenClaw2.7.9,本机离线 AI 解压即用实操文档(含安装包)
  • 成都百达翡丽回收价格查询和各大平台实测排行(2026年7月最新数据) - 诚收名表回收平台
  • 2026年7月 | 国内口碑翡翠吊坠商家盘点:多角度对比选购参考 - 互联网科技品牌测评
  • 2026北京奢侈品上门回收哪家好?毓典本地8家门店全域免费上门 - 旧奢新值
  • 柯尔嫚模式系统小程序开发
  • AI模组价值分层:从无限循环功能看智能创作工具的真伪
  • ChatGPT模拟对话练习进阶手册(附12个不可外泄的Prompt模板)
  • 终极免费方案:如何用Wand-Enhancer完整解锁WeMod专业版功能
  • 谁懂啊!中层述职根本不是写材料,是一年一度的自我渡劫
  • USB设备驱动安装与指示灯状态排查实战指南
  • TDA7468与PIC18F45K22构建专业音频控制系统
  • 外贸AI询盘回复系统深度对比:智能回复、自动跟进、多语言实测
  • 2026 年当下,东港知名的304不锈钢发酵罐定制厂家深度剖析,揭秘!高效发酵的秘密武器,它如何颠覆你的生产线? - 领域鉴赏官
  • 为什么你的ChatGPT行程总漏掉退税点?揭秘地理语义理解偏差与3层校验机制(附2024Q2最新API兼容性清单)
  • 北京积家回收价格查询及各大平台实测排行(2026年7月最新数据) - 嘉价奢侈品回收平台