JavaJUC 并发工具箱:常见类、线程安全集合与死锁

JUC 并发工具箱:常见类、线程安全集合与死锁

java.util.concurrentJUC)可以理解成:多线程开发里“别手搓了,直接用标准件”的工具箱。来看三块最常用的内容:常见类线程安全集合死锁


1. JUC 的常见类:从“手动挡多线程”升级到“自动挡多线程”

1.1 Callable + FutureTask:把“返回值 + 等结果”这件事做正确

先看一个“传统手法”:子线程算完结果,主线程用wait/notify等结果。代码能写,但同步细节多、容易出错。

staticclassResult{publicintsum=0;publicObjectlock=newObject();}publicstaticvoidmain(String[]args)throwsInterruptedException{Resultresult=newResult();Threadt=newThread(()->{intsum=0;for(inti=1;i<=1000;i++)sum+=i;synchronized(result.lock){result.sum=sum;result.lock.notify();}});t.start();synchronized(result.lock){while(result.sum==0){result.lock.wait();}System.out.println(result.sum);}}

这里的关键点是:主线程要在whilewait(),避免“被唤醒后条件其实仍不成立”的情况;而且还需要额外的Result辅助对象来承载共享数据和锁,整体复杂度偏高。

再看 JUC 的“标准答案”:Callable 负责“能返回结果的任务”FutureTask 负责“存结果 + 等结果”

Callable<Integer>callable=newCallable<Integer>(){@OverridepublicIntegercall(){intsum=0;for(inti=1;i<=1000;i++)sum+=i;returnsum;}};FutureTask<Integer>futureTask=newFutureTask<>(callable);Threadt=newThread(futureTask);t.start();intresult=futureTask.get();// 阻塞等待计算完成,并拿到返回值System.out.println(result);

FutureTask就像“取餐小票”:任务什么时候做完不确定,但小票在手,随时get()去等结果/拿结果。


1.2 ReentrantLock:更灵活的互斥锁(但也更考验手法)

ReentrantLocksynchronized都是为了互斥(同一时刻只让一个线程进入临界区)。它的典型用法是:lock 之后必须 finally unlock,否则极容易漏掉解锁造成“锁永久不释放”。

ReentrantLocklock=newReentrantLock();lock.lock();try{// 临界区:访问共享资源}finally{lock.unlock();}

它比synchronized更“可控”的点主要有这些:

  • synchronized是 JVM 内部实现的关键字;ReentrantLock是标准库类(JVM 外、Java 实现)。
  • synchronized获取不到锁会“死等”;ReentrantLock可以tryLock(超时),等一会拿不到就放弃。
  • synchronized是非公平锁;ReentrantLock默认也非公平,但构造时传true可以开公平锁。
  • 等待/唤醒方面:synchronizedwait/notify,唤醒的是“随机等待线程”;ReentrantLock + Condition可以更精确地控制唤醒哪个等待线程。

再给一个tryLock的味道(“死等” vs “等一会儿不行就撤”):

ReentrantLocklock=newReentrantLock();if(lock.tryLock()){try{// 拿到锁了再干活}finally{lock.unlock();}}else{// 拿不到锁:选择降级、重试、记录日志等}

什么时候选哪个?一句话:竞争不激烈图省心 → synchronized;竞争激烈或需要超时/公平/精确唤醒 → ReentrantLock


1.3 原子类 AtomicX:用 CAS 做“无锁原子更新”

原子类内部依赖CAS(Compare-And-Swap)实现,通常比“加锁做 i++”更高效。常见的有:

  • AtomicBoolean
  • AtomicInteger
  • AtomicIntegerArray
  • AtomicLong
  • AtomicReference
  • AtomicStampedReference(名字就暗示:它会带“戳”,常用来对付 ABA)

AtomicInteger为例,常见方法和语义:

  • addAndGet(delta)i += delta
  • incrementAndGet()++i
  • getAndIncrement()i++
  • decrementAndGet()--i
  • getAndDecrement()i--

来看一个最常用的例子:并发计数。

AtomicIntegercnt=newAtomicInteger(0);Runnabler=()->{for(inti=0;i<100000;i++){cnt.getAndIncrement();}};newThread(r).start();newThread(r).start();

它背后的核心逻辑可以理解成一个 CAS 自旋(一直尝试,直到更新成功):

classAtomicInteger{privateintvalue;publicintgetAndIncrement(){intoldValue=value;while(CAS(value,oldValue,oldValue+1)!=true){oldValue=value;}returnoldValue;}}

注意这里的 CAS 体现的是“三元比较交换”:内存位置/当前值、期望旧值、新值,不匹配就重试。


1.4 线程池:ExecutorService / Executors / ThreadPoolExecutor

线程频繁创建销毁不划算,所以线程池的思路是:线程不用了先放“池子”里,下次直接复用。

最常见入口是:

  • ExecutorService:线程池实例
  • Executors:工厂类,快速创建不同风格线程池
  • submit(...):提交任务
ExecutorServicepool=Executors.newFixedThreadPool(10);pool.submit(()->{System.out.println("hello");});

Executors常见创建方式包括:

  • newFixedThreadPool:固定线程数
  • newCachedThreadPool:线程数动态增长
  • newSingleThreadExecutor:单线程
  • newScheduledThreadPool:延迟/定时执行(Timer 的进阶版)

更可控的底层是ThreadPoolExecutor。理解它的参数,可以用“开公司招人”类比:

  • corePoolSize:正式员工数
  • maximumPoolSize:正式员工 + 临时工上限
  • keepAliveTime+unit:临时工空闲多久就辞退
  • workQueue:任务队列(阻塞队列)
  • threadFactory:线程工厂
  • RejectedExecutionHandler:忙不过来时的拒绝策略

拒绝策略常见四种:

  • AbortPolicy():直接抛异常
  • CallerRunsPolicy():调用者自己执行
  • DiscardOldestPolicy():丢队列里最老的任务
  • DiscardPolicy():丢新来的任务

来看一个带拒绝策略的ThreadPoolExecutor示例(这里用SynchronousQueue配合AbortPolicy):

ExecutorServicepool=newThreadPoolExecutor(1,2,1000,TimeUnit.MILLISECONDS,newSynchronousQueue<>(),Executors.defaultThreadFactory(),newThreadPoolExecutor.AbortPolicy());for(inti=0;i<3;i++){pool.submit(()->System.out.println("hello"));}

任务超过负荷时,会按策略处理(这里是直接异常)。


1.5 Semaphore:为什么能像“锁”一样用?

Semaphore是“可用资源个数”的计数器:acquire()申请资源(P 操作),release()释放资源(V 操作)。计数器减到 0 还要申请就会阻塞等待;更关键的是PV 加减计数是原子的,所以可以直接多线程使用。

它能起到“类似锁”的作用,本质是:把“能同时进入临界区的人数”限制住。互斥锁是“最多 1 个”;信号量可以是“最多 N 个”(共享锁)。

Semaphoresemaphore=newSemaphore(4);// 4 个“名额”Runnabler=()->{try{System.out.println("申请资源");semaphore.acquire();// P:名额 -1(没有名额就阻塞)System.out.println("获取到资源");Thread.sleep(1000);System.out.println("释放资源");semaphore.release();// V:名额 +1}catch(InterruptedExceptione){e.printStackTrace();}};for(inti=0;i<20;i++){newThread(r).start();}

前 4 个线程能直接进入“临界区”,后面的线程会在acquire()阻塞,直到有人release();这就是“共享锁”的味道。


1.6 CountDownLatch:等一堆线程干完活再继续

CountDownLatch用来“同时等待 N 个任务结束”:构造时给一个计数 N;每个任务结束countDown();主线程await()等计数归零。

CountDownLatchlatch=newCountDownLatch(10);Runnabler=()->{try{Thread.sleep((long)(Math.random()*10000));latch.countDown();// 一个任务完成,计数 -1}catch(Exceptione){e.printStackTrace();}};for(inti=0;i<10;i++){newThread(r).start();}// 必须等到 10 个都完成latch.await();System.out.println("比赛结束");

这段代码的语义非常直白:“不到 10 人全回来,不公布成绩”


2. 线程安全的集合类:别拿 HashMap 去硬刚并发

2.1 先定个基调:哪些“天生线程安全”,哪些不是

很多集合默认不是线程安全的;但Vector / Stack / Hashtable是线程安全的(不过不太建议用),其他大多数集合类都不是线程安全的。


2.2 多线程环境下怎么用 ArrayList:三条路

路 1:自己加锁synchronizedReentrantLock):

List<Integer>list=newArrayList<>();Objectlock=newObject();Runnabler=()->{for(inti=0;i<1000;i++){synchronized(lock){list.add(i);}}};

路 2:Collections.synchronizedList(标准库给的“加了 synchronized 的 List 包装器”):

List<Integer>list=Collections.synchronizedList(newArrayList<>());Runnabler=()->{for(inti=0;i<1000;i++){list.add(i);// 内部关键方法都带 synchronized}};

路 3:CopyOnWriteArrayList(写时复制:读写分离)

它的核心思想是:写的时候不在原容器上改,而是 copy 出新容器,写完再把引用指向新容器;读则读旧容器,因此读不需要加锁竞争,适合“读多写少”。代价也很明显:更吃内存,而且新写入的数据不会第一时间被读到。

List<Integer>list=newCopyOnWriteArrayList<>();// 读多写少的场景更合适list.add(1);System.out.println(list.get(0));

2.3 多线程环境下用队列:BlockingQueue 家族直接上

多线程里最常见的模式之一是“生产者-消费者”,这时候用阻塞队列非常省心。常见阻塞队列包括:

  • ArrayBlockingQueue:数组实现
  • LinkedBlockingQueue:链表实现
  • PriorityBlockingQueue:堆实现(带优先级)
  • TransferQueue:交接型队列(用来做更强的“线程间移交”)

来看一个最经典的生产者-消费者:

BlockingQueue<String>q=newArrayBlockingQueue<>(3);// 生产者:放newThread(()->{try{for(inti=0;i<10;i++){q.put("msg-"+i);// 满了会阻塞System.out.println("put "+i);}}catch(InterruptedExceptione){e.printStackTrace();}}).start();// 消费者:取newThread(()->{try{while(true){Stringv=q.take();// 空了会阻塞System.out.println("take "+v);}}catch(InterruptedExceptione){e.printStackTrace();}}).start();

这类代码的好处是:阻塞/唤醒由队列内部完成,业务线程不用手搓wait/notify


2.4 多线程环境下用哈希表:Hashtable vs ConcurrentHashMap

HashMap不是线程安全的。并发场景下常用两种:

  • Hashtable:给关键方法加了synchronized,锁住的是整个Hashtable对象,效率偏低,key 不允许为 null。

  • ConcurrentHashMap:线程安全,并且为了降低锁竞争做了不少优化:

    • JDK 1.7 用“分段锁”(Segment)降低冲突概率(同段才竞争)。
    • JDK 1.8 取消分段锁,改为“每个桶/链表一把锁”(以链表头结点作为锁对象);结构从“数组+链表”升级为“数组+链表/红黑树”,链表长到一定程度(≥8)会转红黑树;并提到会充分利用 CAS、优化扩容方式,key 不允许为 null。

看一个并发 map 的典型用法:

ConcurrentHashMap<String,Integer>map=newConcurrentHashMap<>();// 并发下“如果没有就放一个默认值”常用 computeIfAbsentmap.computeIfAbsent("k",key->0);// 原子式更新(避免 read-modify-write 的竞态)map.compute("k",(key,oldVal)->oldVal+1);

3. 死锁:线程界的“互相礼让到世界毁灭”

3.1 死锁是什么:线程都卡住了,程序不可能正常结束

死锁就是:多个线程同时被阻塞,一个或全部都在等待某个资源被释放,结果谁也不撒手,线程无限期阻塞,程序无法正常终止。

理解死锁最形象的例子:吃饺子要酱油和醋,两个人一人拿一个,都要求对方先给自己——互不相让,直接卡死。酱油/醋是两把锁,两个人是两个线程。

进一步还有经典“哲学家就餐问题”:如果大家同一时刻都先拿左边筷子,再拿右边筷子,就会发现右边都被占了,于是全员等待,全员死锁。


3.2 死锁产生的四个必要条件(面试必背但更要会用)

死锁成立需要四个条件同时满足:

  1. 互斥使用:资源被一个线程占有时,其他线程不能用
  2. 不可抢占:资源只能由占有者主动释放,不能强抢
  3. 请求并保持:拿着已有资源,还要继续请求新的资源
  4. 循环等待:形成环路:P1 等 P2 的资源,P2 等 P3 的资源,…,Pn 又等回 P1

只要打破任意一个条件,死锁就消失。最容易下手的是:破坏循环等待


3.3 如何避免死锁:锁排序(Lock Ordering)

最常用的办法:给锁编号(1…M),所有线程必须按编号从小到大加锁,这样就不会形成等待环路。

来看一段“容易死锁”的代码:两个线程加锁顺序相反。

Objectlock1=newObject();Objectlock2=newObject();Threadt1=newThread(()->{synchronized(lock1){synchronized(lock2){// do something...}}});Threadt2=newThread(()->{synchronized(lock2){synchronized(lock1){// do something...}}});t1.start();t2.start();

如果 t1 拿到 lock1、t2 拿到 lock2,然后双方都去等对方的第二把锁,就卡死。

修复方式就是“约定顺序”:都先 lock1 再 lock2。

Objectlock1=newObject();Objectlock2=newObject();Threadt1=newThread(()->{synchronized(lock1){synchronized(lock2){// do something...}}});Threadt2=newThread(()->{synchronized(lock1){synchronized(lock2){// do something...}}});t1.start();t2.start();

锁顺序一致,就不会出现环路等待。


总结

  • 需要“有返回值的任务 + 等结果” →Callable + FutureTask(同步细节少很多)。
  • 读多写少的共享 List →CopyOnWriteArrayList;生产者消费者 →BlockingQueue家族;并发 Map →ConcurrentHashMap
  • 看到“多把锁 + 加锁顺序不一致”就要警觉:锁排序是最常用的死锁规避手段。

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

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

相关文章

开源翻译模型新标杆:HY-MT1.5-7B生产环境部署实战

开源翻译模型新标杆&#xff1a;HY-MT1.5-7B生产环境部署实战 在大模型推动自然语言处理快速演进的背景下&#xff0c;高质量、低延迟、可本地化部署的机器翻译系统正成为企业级应用的核心需求。腾讯近期开源的混元翻译模型 HY-MT1.5 系列&#xff0c;凭借其卓越的多语言支持能…

Relight:AI照片光影重塑神器,30秒焕新光线氛围

Relight&#xff1a;AI照片光影重塑神器&#xff0c;30秒焕新光线氛围 【免费下载链接】Relight 项目地址: https://ai.gitcode.com/hf_mirrors/dx8152/Relight 导语&#xff1a;基于Qwen-Image-Edit-2509模型开发的Relight插件&#xff0c;通过LoRa技术实现照片光影的…

STM32 USB虚拟串口配置:手把手教程

STM32 USB虚拟串口&#xff1a;从协议原理到工程落地的完整实践 在嵌入式开发中&#xff0c;你是否曾为调试信息输出而烦恼&#xff1f; 是不是每次都要接UART、找电平转换芯片、再连串口工具才能看到一行 printf("Hello World\n") &#xff1f; 更别提项目后期多…

Fusion_lora:AI图像融合新工具,产品溶图效果惊艳

Fusion_lora&#xff1a;AI图像融合新工具&#xff0c;产品溶图效果惊艳 【免费下载链接】Fusion_lora 项目地址: https://ai.gitcode.com/hf_mirrors/dx8152/Fusion_lora 导语&#xff1a;一款名为Fusion_lora的AI图像融合新工具近日受到关注&#xff0c;其基于Qwen-I…

HY-MT1.5网页推理接口开发:REST API封装教程

HY-MT1.5网页推理接口开发&#xff1a;REST API封装教程 1. 引言 1.1 腾讯开源翻译大模型HY-MT1.5的技术背景 随着全球化进程的加速&#xff0c;高质量、低延迟的机器翻译需求日益增长。传统商业翻译API虽然成熟&#xff0c;但在定制化、数据隐私和部署灵活性方面存在局限。…

Qwen-Image重磅发布:AI绘图实现精准文本渲染与编辑

Qwen-Image重磅发布&#xff1a;AI绘图实现精准文本渲染与编辑 【免费下载链接】Qwen-Image 我们隆重推出 Qwen-Image&#xff0c;这是通义千问系列中的图像生成基础模型&#xff0c;在复杂文本渲染和精准图像编辑方面取得重大突破。 项目地址: https://ai.gitcode.com/hf_mi…

HY-MT1.5-1.8B低延迟优化:边缘计算部署全攻略

HY-MT1.5-1.8B低延迟优化&#xff1a;边缘计算部署全攻略 随着多语言交互需求的爆发式增长&#xff0c;高效、精准且低延迟的翻译模型成为智能设备、实时通信和边缘计算场景的核心支撑。腾讯开源的混元翻译大模型HY-MT1.5系列&#xff0c;凭借其在翻译质量与推理效率之间的卓越…

HY-MT1.5-1.8B量化部署:Jetson设备运行指南

HY-MT1.5-1.8B量化部署&#xff1a;Jetson设备运行指南 1. 引言 随着边缘计算和实时翻译需求的不断增长&#xff0c;轻量级、高性能的翻译模型成为智能硬件落地的关键。腾讯开源的混元翻译大模型 HY-MT1.5 系列&#xff0c;凭借其卓越的语言覆盖能力和翻译质量&#xff0c;迅速…

Ling-flash-2.0开源:6B参数打造极速推理新标杆!

Ling-flash-2.0开源&#xff1a;6B参数打造极速推理新标杆&#xff01; 【免费下载链接】Ling-flash-2.0 项目地址: https://ai.gitcode.com/hf_mirrors/inclusionAI/Ling-flash-2.0 导语&#xff1a;inclusionAI正式开源新一代混合专家模型Ling-flash-2.0&#xff0c;…

HY-MT1.5-7B术语一致性:品牌命名规范维护

HY-MT1.5-7B术语一致性&#xff1a;品牌命名规范维护 1. 引言 随着全球化进程的加速&#xff0c;高质量、多语言互译能力已成为企业出海、跨文化交流和智能产品本地化的核心需求。在这一背景下&#xff0c;腾讯开源了混元翻译大模型系列——HY-MT1.5&#xff0c;旨在提供高精…

克拉泼振荡电路Multisim仿真:新手入门必看指南

克拉泼振荡电路Multisim仿真&#xff1a;从零开始的高频正弦波设计实战你是否曾为一个简单的LC振荡电路在面包板上“死活不起振”而抓耳挠腮&#xff1f;是否在示波器前等了十几秒&#xff0c;只看到一片噪声或一条直线&#xff1f;又或者&#xff0c;面对复杂的晶体管寄生参数…

搞懂这些术语,你就超过 80% 的新手

机器学习基础概念监督学习&#xff08;Supervised Learning&#xff09;指通过标注数据训练模型&#xff0c;使其能够预测未知数据的输出。常见算法包括线性回归、逻辑回归、决策树等。标注数据意味着每个训练样本都有对应的正确答案。无监督学习&#xff08;Unsupervised Lear…

腾讯HY-MT1.5实战:全球化SaaS产品翻译方案

腾讯HY-MT1.5实战&#xff1a;全球化SaaS产品翻译方案 随着全球化业务的加速推进&#xff0c;SaaS类产品对多语言支持的需求日益迫切。传统商业翻译API虽能提供基础服务&#xff0c;但在定制化、数据隐私和成本控制方面存在明显短板。腾讯近期开源的混元翻译大模型 HY-MT1.5 系…

HY-MT1.5-1.8B优化:边缘设备功耗控制

HY-MT1.5-1.8B优化&#xff1a;边缘设备功耗控制 1. 引言&#xff1a;轻量大模型在边缘计算中的新突破 随着多语言交流需求的快速增长&#xff0c;高质量、低延迟的实时翻译能力正成为智能终端的核心竞争力之一。然而&#xff0c;传统大模型依赖云端推理&#xff0c;面临网络…

HY-MT1.5-7B推理加速:大模型部署优化策略

HY-MT1.5-7B推理加速&#xff1a;大模型部署优化策略 1. 背景与技术演进 随着多语言交流需求的快速增长&#xff0c;高质量、低延迟的机器翻译系统成为智能应用的核心组件。腾讯推出的混元翻译模型&#xff08;HY-MT&#xff09;系列&#xff0c;作为面向实际场景优化的大规模…

spring boot 项目打印sql日志和结果,使用logback或配置文件

在 Spring Boot 项目中使用 MyBatis 或 MyBatis-Plus 作为持久化框架时&#xff0c;你可以通过配置 Logback 来打印 SQL 日志及其结果。以下是一些具体的步骤和配置示例&#xff1a; 配置 Logback 以打印 MyBatis 或 MyBatis-Plus 的 SQL 日志创建或修改 Logback 配置文件&…

HY-MT1.5-1.8B实时语音翻译系统集成指南

HY-MT1.5-1.8B实时语音翻译系统集成指南 随着多语言交流需求的不断增长&#xff0c;高效、准确且低延迟的实时翻译系统成为智能硬件与全球化服务的核心组件。腾讯开源的混元翻译大模型HY-MT1.5系列&#xff0c;凭借其在翻译质量、部署灵活性和功能丰富性上的突出表现&#xff…

数据安全公司Cyera融资4亿美元 估值90亿美元

雷递网 乐天 1月10日数据安全公司Cyera日前宣布&#xff0c;公司已完成4亿美元融资&#xff0c;估值达到90亿美元Cyera在2024年11月的一轮融资中估值为30亿美元&#xff0c;并在2025年6月的上一轮融资中估值飙升至60亿美元&#xff0c;当时融资额为5.4亿美元。Cyera此轮融资由黑…

RaNER模型实战:新闻事件实体关系抽取案例

RaNER模型实战&#xff1a;新闻事件实体关系抽取案例 1. 引言&#xff1a;AI 智能实体侦测服务的现实需求 在信息爆炸的时代&#xff0c;新闻文本、社交媒体内容和公开报告中蕴含着海量的非结构化数据。如何从中快速提取出关键信息——如涉及的人物、地点、组织机构及其相互关…

国民技术冲刺港股:9个月营收9.6亿亏7575万 大股东孙迎彤持股不足3%

雷递网 雷建平 1月10日国民技术股份有限公司&#xff08;简称&#xff1a;“国民技术”&#xff09;日前递交招股书&#xff0c;准备在港交所上市。国民技术已在A股上市&#xff0c;截至今日收盘&#xff0c;国民技术股价为21.31元&#xff0c;市值为124亿元。一旦在A股上市&am…