一、JUC
JUC 是 java.util.concurrent 包的缩写,是 Java 并发编程的核心工具包。它从 JDK 1.5 开始引入,为解决多线程并发问题提供了强大的组件。
1.1、JUC核心架构
java
java.util.concurrent
├── atomic // 原子类
├── locks // 锁框架
├── collections // 并发集合
├── executors // 线程池框架
└── tools // 同步工具
1.2、原子类
核心原理:CAS
java
// CAS 伪代码
boolean compareAndSwap(V, A, B):
if (V == A):
V = B
return true
return false
原子类分类

使用示例:
java
// AtomicInteger 基本用法
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // ++i
counter.getAndIncrement(); // i++
// LongAdder - 适合高并发写多读少的场景
LongAdder adder = new LongAdder();
adder.increment();
long sum = adder.sum();
// 解决 ABA 问题 - 带版本号的原子引用
AtomicStampedReference<String> ref =
new AtomicStampedReference<>("A", 0);
1.3、锁框架
ReentrantLock定义
可重入互斥锁,和synchronized定位类似,都是用来实现互斥效果,保证线程安全。
ReentrantLock的用法:
- lock():加锁,如果获取不到锁就死等。
- trylock(超时时间):加锁,如果获取不到锁,等待一定的时间之后就放弃加锁。
- unlock():解锁
ReentrantLock和synchronized的区别
-
synchronized是一个关键字,是JVM内部实现的(大概率是基于C++实现),ReentrantLock是标准库的一个类,在JVM外实现的(基于Java实现)。 -
synchronized使用时不需要手动释放锁。ReentrantLock使用时需要手动释放。使用起来更灵活,但是也容易遗漏unlock。 -
synchronized在申请锁失败时,会死等。ReentrantLock可以通过trylock的方式等待一段时间就放弃。 -
synchronized是非公平锁,ReentrantLock默认是非公平锁.,可以通过构造方法传入一个true开启公平锁模式。 -
更强大的唤醒机制.。
synchronized是通过Object的wait/notify实现等待-唤醒。每次唤醒的是一个随机等待的线程。ReentrantLock搭配Condition类实现等待-唤醒,可以更精确控制唤醒某个指定的线程.
如何选择使用哪个锁?
-
锁竞争不激烈的时候,使用
synchronized,效率更高,自动释放更方便。 -
锁竞争激烈的时候,使用
ReentrantLock,搭配trylock更灵活控制加锁的行为,而不是死等。 -
如果需要使用公平锁,使用ReentrantLock。
1.4、线程池
核心接口
java
Executor (顶层接口)
├── ExecutorService (核心接口)
│ ├── ThreadPoolExecutor // 最常用
│ └── ScheduledExecutorService
│ └── ScheduledThreadPoolExecutor
└── ForkJoinPool (分治框架)
ThreadPoolExecutor 七大参数
java
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
任务执行流程
java
提交任务
↓
核心线程是否已满? ──否──→ 创建核心线程执行
↓是
任务队列是否已满? ──否──→ 加入队列等待
↓是
最大线程是否已满? ──否──→ 创建非核心线程执行
↓是
执行拒绝策略
内置线程池

拒绝策略
java
// 四种内置策略
AbortPolicy // 抛异常(默认)
CallerRunsPolicy // 调用者线程执行
DiscardPolicy // 丢弃,不抛异常
DiscardOldestPolicy // 丢弃队列头,重提任务
1.5、Semaphore(信号量)
信号量,用来表示"可用资源的个数"。本质上就是⼀个计数器。
理解信号量
可以把信号量想象成是停车场的展示牌:当前有车位100个。表示有100个可用资源。
当有车开进去的时候,就相当于申请一个可用资源,可用车位就-1(这个称为信号量的P操作)。
当有车开出来的时候,就相当于释放一个可用资源,可用车位就+1(这个称为信号量的V操作)。
如果计数器的值已经为0了,还尝试申请资源,就会阻塞等待,直到有其他线程释放资源。
Semaphore的PV操作中的加减计数器操作都是原子的,可以在多线程环境下直接使用。
代码示例
java
// 场景:限流,控制并发数
Semaphore semaphore = new Semaphore(5); // 最多5个线程同时访问
void access() {
semaphore.acquire(); // 获取许可
try {
// 业务代码
} finally {
semaphore.release(); // 释放许可
}
}
二、线程安全的集合类
在 Java 中,线程安全的集合主要分为三类:早期的线程安全集合、同步包装器、JUC 并发集合。
2.1、早期线程安全集合

代码示例:
java
// 示例:Vector 线程安全但性能差
Vector<String> vector = new Vector<>();
vector.add("a"); // 内部 synchronized
String s = vector.get(0);
缺点: 所有方法都加同一把锁(对象锁),并发度极低,即使读操作也互斥。
2.2、同步包装器
Collections.synchronizedXXX() 方法可以将普通集合包装成线程安全集合。

特点:
-
基于
synchronized代码块,锁对象可指定(默认是包装后的集合对象)。 -
迭代器不是线程安全的,遍历时必须加锁。
-
性能依然较差,因为所有操作都串行化。
2.3、JUC 并发集合(java.util.concurrent 包)
现代 Java 并发编程的首选,提供了细粒度锁、无锁算法等高效实现。
2.3.1、ConcurrentHashMap
线程安全的哈希表,JDK 1.8+ 采用 CAS + synchronized 锁头结点,并发度极高。
特点:
-
不允许 null 键/值。
-
迭代器是弱一致性的(不抛出
ConcurrentModificationException,但可能不反映最新修改)。 -
适用于高并发读写。
2.3.2、CopyOnWriteArrayList / CopyOnWriteArraySet
写时复制 思想:写操作复制一个新数组,修改完成后替换原数组;读操作在原数组上进行,无需加锁。
特点:
-
读多写少场景极佳(如配置信息、监听器列表)。
-
写操作开销大(数组复制),不适合写频繁的场景。
-
迭代器是快照风格,不会抛出
ConcurrentModificationException。
2.3.3、ConcurrentLinkedQueue / ConcurrentLinkedDeque
无界非阻塞队列,基于 CAS 实现。
特点:
-
无锁、高吞吐。
-
不阻塞,适合生产者-消费者模型但不需要阻塞等待的场景。
-
迭代器弱一致性。
2.3.4、BlockingQueue 接口及其实现
阻塞队列: 当队列满时,put() 阻塞;当队列空时,take() 阻塞。常用于生产者-消费者模式。

2.3.5、ConcurrentSkipListMap / ConcurrentSkipListSet
基于跳表的并发有序 Map/Set,类似于 TreeMap 的并发版本。
特点:
-
线程安全且有序。
-
无锁(CAS),并发性能优于
Collections.synchronizedSortedMap。 -
提供
lowerKey,ceilingKey等导航方法。
三、死锁
死锁是并发编程中的一种严重问题:两个或更多线程互相持有对方需要的资源,且都不释放自己占有的资源,导致所有线程都无法继续执行。
3.1、死锁的四个必要条件
四个条件必须同时满足才会发生死锁:
-
互斥条件: 资源一次只能被一个线程占用。
-
请求与保持条件: 线程持有至少一个资源,同时又在请求其他资源。
-
不可剥夺条件: 线程已获得的资源,在使用完之前不能被其他线程强行剥夺。
-
循环等待条件: 存在一个线程等待环,例如 T1 等待 T2 的资源,T2 等待 T3 的资源,...,Tn 等待 T1 的资源。
3.2、死锁的避免与预防
-
破坏"请求与保持"条件:一次性申请所有资源
-
破坏"不可剥夺"条件:使用可超时的锁
-
破坏"循环等待"条件:按固定顺序获取锁
-
使用高级并发工具(使用
ConcurrentHashMap、BlockingQueue等,避免显式加锁;使用Semaphore、CountDownLatch等,减少锁竞争。) -
避免嵌套锁(尽量减少同步块内调用外部方法(可能持有其他锁);能不用多个锁就不用。)