【JUC 并发编程】
·
一、什么是JUC(java.util.concurrent)
1.定义
JUC = Java并发工具包,是JDK5引入的一整套高性能并发解决方案。在多线程环境下,安全、高效、可控地操作共享资源。
2.JUC解决了什么问题?
| 问题 | 传统方式 | JUC方式 |
|---|---|---|
| 线程安全 | synchronized | Lock / 原子类 |
| 线程通信 | wait / notify | Condition |
| 线程池 | 手写 | Executor |
| 并发容器 | Vector | ConcurrentHashMap |
| 并发协作 | 无 | CountDownLatch等 |
二、进程 VS 线程
1.进程
- 资源分配的最小单位
- 拥有独立的内存空间、文件描述符、堆、方法区
2.线程
- CPU调度的最小单位
- 线程之间共享进程资源
- 共享:
(1)堆
(2)方法区 - 独享:
(1)栈
(2)程序计数器
进程负责资源,线程负责执行
三、Lock锁
1.synchronized 的问题
- 只有一把锁
- 无法中断
- 无法公平
- 锁粒度粗
- 无法尝试加锁
2.Lock接口(核心:ReentrantLock)
2.1 基本使用
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}
2.2 相比 synchronized 的优势
| 能力 | Lock | synchronized |
|---|---|---|
| 是否可中断 | 是 | 否 |
| 是否公平锁 | 是 | 否 |
| 是否可以尝试加锁 | tryLock | 否 |
| 是否多条件 | Condition | 否 |
2.3 公平锁 vs 非公平锁
new ReentrantLock(true); // 公平锁
new ReentrantLock(false); // 非公平锁(默认)
- 公平锁:按等待时间顺序
- 非公平锁:吞吐量更高
2.4 Condition(精确唤醒)
Condition condition = lock.newCondition();
condition.await();
condition.signal();
替代wait / notify,支持多个条件队列
PS:synchronized 是 JVM 层锁,Lock 是 JDK 层锁,Lock 更灵活、功能更强,生产推荐。
四、生产者-消费者问题(并发通信核心)
1.经典问题
- 生产者生产数据
- 消费者消费数据
- 中间容器有容量限制
2.使用Lock + Condition实现
class Resource {
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
public void produce() throws InterruptedException {
lock.lock();
try {
while (count == 1) {
notFull.await();
}
count++;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
count--;
notFull.signal();
} finally {
lock.unlock();
}
}
}
关键点:while防止虚假唤醒(if会导致虚假唤醒),精准唤醒
五、8锁现象
本质:多个synchronized方法,锁的是谁?
- 普通方法 -> 锁的是对象
- static方法 -> 锁的是Class
- 两者互不影响
- 不同对象互不影响
synchronized 锁的是对象监视器(Monitor)
六、集合类不安全问题
1.问题示例
List<String> list = new ArrayList<>();
list.add("a"); // 多线程会抛异常
常见异常:
- ConcurrentModificationException
- 数据覆盖
- 数据丢失
2.解决方案对比
| 方案 | 是否推荐 |
|---|---|
| Vector | 否 |
| Collections.synchronizedList | 否 |
| CopyOnWriteArrayList | 是 |
List<String> list = new CopyOnWriteArrayList<>();
原理:
- 写时复制
- 读无锁
- 适合:读多写少
3.Map解决方案
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
七、Callable(有返回值的线程)
1.Runnable vs Callable
| Runnable | callable | |
|---|---|---|
| 是否有返回值 | 否 | 是 |
| 是否有异常 | 否 | 是 |
| 配合使用 | Thread | FutureTask |
2.使用方式
Callable<Integer> task = () -> 100;
FutureTask<Integer> future = new FutureTask<>(task);
new Thread(future).start();
Integer result = future.get();
八、JUC常用辅助类
1.CountDownLatch(倒计时)
等所有线程执行完,再执行主线程
CountDownLatch latch = new CountDownLatch(3);
latch.await();
场景:服务启动校验、并行任务聚合
2.CyclicBarrier(循环屏障)
一组线程互相等待
CyclicBarrier barrier = new CyclicBarrier(3);
barrier.await();
场景:多线程分阶段计算
3.Semaphore(信号量)
Semaphore semaphore = new Semaphore(3);
场景:
- 限流
- 连接池
- 线程并发控制
九、读写锁(ReentrantReadWriteLock)
1.核心思想
- 读读并发
- 读写互斥
- 写写互斥
ReadWriteLock rwLock = new ReentrantReadWriteLock();
场景:缓存系统、配置读取
十、阻塞队列(重点)
1.什么是阻塞队列
- 生产者放满 -> 阻塞
- 消费者取空 -> 阻塞
天然实现生产者 - 消费者
2.四组API
| 方法组 | 行为 |
|---|---|
| add / remove | 抛异常 |
| offer / poll | 返回值 |
| put / task | 阻塞 |
| offee(timeout) | 超时 |
3.常见实现
| 队列 | 场景 |
|---|---|
| ArrayBlockingQueue | 有界 |
| LinkedBlockingQueue | 高并发 |
| SynchronousQueue | 直接交付 |
| DelayQueue | 延迟任务 |
线程池底层就是BlockingQueue
十一、应用场景总结
| 技术实现 | 场景 |
|---|---|
| ReentrantLock | 复杂并发控制 |
| Condition | 精准线程通信 |
| ConcurrentHashMap | 高并发缓存 |
| CountDownLatch | 启动依赖 |
| Semaphore | 限流 |
| ReadWriteLock | 配置/缓存 |
| BlockingQueue | MQ、线程池 |
十二、总结
1.synchronized 和 Lock 区别?
Lock更灵活,支持公平锁、中断、条件队列,推荐Lock
2.为什么要用while而不是if
防止虚假唤醒,确保线程安全
3.ConcurrentHashMap 为什么线程安全?
JDK8 使用 CAS + synchronized + Node 锁,锁粒度更细
4.BlockingQueue 的作用?
解耦生产消费,避免手写 wait/notify
5.读写锁适合什么场景?
读多写少的共享资源,如缓存
6.虚假唤醒
线程在没有满足唤醒条件的情况下,被从 wait / await 状态中唤醒,这种现象就叫虚假唤醒。
注意:不是 notify/ signal 发错了,而是“线程醒了,但条件并不成立”
1.虚假唤醒发生在哪里?
- Object.wait()
- Condition.await()
- LockSupport.park()
更多推荐




所有评论(0)