JUC
→ 返回 Java 基础 → 相关:对象(总览)· JMM(hb、volatile、安全发布 — 先理解规范再看 API)· JVM(运行时内存结构、对象头锁状态)· 反射(动态代理底层)
java.util.concurrent — Java 并发工具包。
对象主题边界:本文讲如何用锁、wait、volatile 操作堆上实例;对象头位域、分配与回收见 JVM / 垃圾回收;hb 与安全发布见 JMM;总览 对象。
线程基础
创建线程的方式
// 1. 继承 Thread
class MyThread extends Thread {
public void run() { ... }
}
new MyThread().start();
// 2. 实现 Runnable(推荐,解耦任务和线程)
Thread t = new Thread(() -> System.out.println("running"));
t.start();
// 3. 实现 Callable(有返回值,可抛异常)
FutureTask<String> task = new FutureTask<>(() -> "result");
new Thread(task).start();
String result = task.get(); // 阻塞直到完成
// 4. 线程池(生产环境首选)
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> { ... });线程状态
NEW → RUNNABLE → BLOCKED / WAITING / TIMED_WAITING → TERMINATED
| 状态 | 触发 |
|---|---|
| NEW | 创建未 start |
| RUNNABLE | start() 后,包含就绪和运行中 |
| BLOCKED | 等待 synchronized 锁 |
| WAITING | wait() / join() / LockSupport.park() |
| TIMED_WAITING | sleep(n) / wait(n) |
| TERMINATED | run() 结束 |
synchronized
作用:保证同一时刻只有一个线程执行被锁保护的代码块。
// 修饰实例方法:锁 this
public synchronized void method() { ... }
// 修饰静态方法:锁 Class 对象
public static synchronized void staticMethod() { ... }
// 修饰代码块:锁指定对象
synchronized (this) { ... }
synchronized (MyClass.class) { ... }锁升级(Lock Escalation)
synchronized 的互斥由 JVM 在对象头 Mark Word 上实现(布局与 lock bits 见 对象头(Mark Word))。HotSpot 默认按从轻到重尝试,竞争加剧时膨胀;一般不会把重量级锁再降回轻量级。
无锁 → 偏向锁(JDK 18 前)→ 轻量级锁(CAS + 自旋)→ 重量级锁(Monitor + OS 互斥量)
┌─────────────────────────────────────┐
│ 无锁(Normal) │
└──────────────┬──────────────────────┘
│ 线程进入 synchronized
┌───────────────────────┼───────────────────────┐
▼ (JDK 8~14 默认偏向) ▼ 无偏向或已撤销 │
┌──────────────┐ ┌──────────────┐ │
│ 偏向锁 │ │ 轻量级锁 │◄─────────────┘
└──────┬───────┘ └──────┬───────┘
│ 其他线程竞争、撤销偏向 │ CAS 自旋失败、激烈竞争
└──────────┬───────────────┘
▼
┌──────────────┐
│ 重量级锁 │ ← ObjectMonitor,线程 BLOCKED
└──────────────┘
四种状态
| 状态 | 典型场景 | 实现要点 |
|---|---|---|
| 无锁 | 无人竞争 | Mark Word 存哈希、GC 年龄等 |
| 偏向锁 | 长期仅一个线程反复加锁 | 记录偏向线程 ID,重入常无需 CAS(JDK 15 废弃,JDK 18 移除) |
| 轻量级锁 | 有竞争但不激烈、持锁时间短 | 栈上 Lock Record + CAS;自适应自旋(JDK 6+) |
| 重量级锁 | 自旋失败、多线程阻塞、wait | ObjectMonitor;未抢到锁的线程在 OS 层阻塞 |
升级触发(简化)
- 偏向锁:别的线程来抢 → 偏向撤销(Revoke,可能有 STW)→ 往往进入轻量级或竞争路径。高并发常
-XX:-UseBiasedLocking(JDK 8~14),见 线程与并发参数。 - 轻量级锁:CAS 失败或自旋耗尽 → 膨胀为重量级。
- 重量级锁:
wait()释放 Monitor 并挂起;可见性见 synchronized(与轻量/重量无关)。
与 JIT、JUC 其它锁的区别
| 机制 | 是否走 Mark Word 锁升级 |
|---|---|
synchronized | ✅ 是 |
ReentrantLock / AQS | ❌ 否,Java 层 volatile + CAS + 队列 |
| 锁消除 / 锁粗化(JIT) | 编译期去掉或合并 synchronized,见 JIT |
实践提示
- 低竞争、短临界区:轻量级 + 自旋通常够用。
- 高竞争:尽快变重量级,应缩小锁粒度或改用 JUC 无锁/队列结构。
- 勿在
synchronized内sleep占锁 → Thread.sleep() 与 Object.wait() 的区别。
synchronized 与 Monitor(对象头)
synchronized 锁的是某个对象实例(或 Class),字节码为 monitorenter / monitorexit,与 Monitor 绑定;状态记在 Mark Word(上文「锁升级」)。
- Mark Word 位域:对象头(Mark Word)
- 索引:堆上的内存布局(摘要)
synchronized(obj) → 进入 obj 的 Monitor → 临界区 → monitorexit 释放
- 实例方法:锁
this - 静态方法:锁 Class 对象
- 代码块
synchronized(x):锁 x 引用的实例
wait / notify / notifyAll
Object.wait() / notify() / notifyAll() 是 Monitor 上的条件变量 API,必须在已持有该对象 Monitor 的线程里调用(即在 synchronized (obj) 块内),否则抛 IllegalMonitorStateException。
| 方法 | 作用 |
|---|---|
wait() | 释放 obj 的 Monitor,当前线程进入 WAITING,直到被 notify / notifyAll 或中断 |
wait(long timeout) | 同上,超时后自动醒来(仍须重新竞争锁) |
notify() | 唤醒 一个 在 同一 obj 上 wait 的线程(具体哪个由 JVM 决定) |
notifyAll() | 唤醒所有在该 obj 上 wait 的线程 |
典型协作模式(用 while 防虚假唤醒、防条件未满足就继续):
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait(); // 释放 queue 的锁,进入 WAITING
}
Object item = queue.remove();
}
synchronized (queue) {
queue.add(item);
queue.notifyAll(); // 或 notify(),视场景而定
}wait() 被唤醒后不会立刻执行后续代码:线程先回到 BLOCKED,重新竞争 synchronized (obj) 的 Monitor,拿到锁后才从 wait() 返回继续执行。
Thread.sleep() 与 Object.wait() 的区别
两者都会阻塞当前线程,但语义完全不同:
| 对比项 | Thread.sleep(ms) | Object.wait() / wait(ms) |
|---|---|---|
| 所属类 | Thread 静态方法 | Object 实例方法 |
| 是否释放锁 | 不释放(仍持有已拿到的 synchronized 锁) | 释放 调用 wait() 时所在对象的 Monitor |
| 唤醒方式 | 超时到期,或 interrupt() | notify / notifyAll,或超时,或 interrupt() |
| 使用前提 | 任意线程、任意位置 | 必须在 已持有该对象锁 的 synchronized 块内 |
| 线程状态 | TIMED_WAITING(带时间) | WAITING(无参)或 TIMED_WAITING(带超时) |
| 设计目的 | 让出 CPU 时间片,不参与 锁协作 | 在条件不满足时挂起,把锁让给别的线程改共享状态 |
// ❌ 在 synchronized 里 sleep:其他线程仍拿不到锁,无法 notify,易死锁
synchronized (lock) {
Thread.sleep(1000); // 占着锁睡 1 秒
}
// ✅ wait:释放 lock,其他线程可进入 synchronized(lock) 并 notify
synchronized (lock) {
while (!condition) {
lock.wait();
}
}与 JMM 的关系:wait() 释放 Monitor → monitor exit → happens-before;sleep() 不建立该路径。安全发布与 hb 规则见 wait 的关系。
中断:两者均响应 interrupt() → InterruptedException。
sleep / wait 与锁:线程与对象(摘要)。生产环境优先 Lock + Condition、阻塞队列等,少用手写 wait/notify。
volatile
JMM 语义(可见性 + 有序性 + 不保证原子性):volatile。本文只列工程用法。
作用:
- 可见性:写对后续读建立 happens-before
- 禁止部分重排序(如 DCL 单例)
不能保证原子性(volatile int i; i++ 仍然线程不安全)。
// 常见用法:状态标志位
volatile boolean running = true;
// DCL 双重检查锁(单例)
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // volatile 防止指令重排序
}
}
}
return instance;
}Lock / ReentrantLock
比 synchronized 更灵活:
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock(); // 必须在 finally 释放
}
// 尝试加锁(不阻塞)
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try { ... } finally { lock.unlock(); }
}ReentrantLock vs synchronized:
| synchronized | ReentrantLock | |
|---|---|---|
| 锁类型 | 非公平 | 公平/非公平可选 |
| 可中断 | ❌ | ✅ lockInterruptibly() |
| 超时获取 | ❌ | ✅ tryLock(timeout) |
| 条件变量 | wait/notify | Condition |
| 自动释放 | ✅ | ❌ 需手动 unlock |
原子类(Atomic)
基于 CAS(Compare And Swap) 实现,无锁线程安全:
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // ++,原子操作
count.compareAndSet(1, 2); // 如果当前值是1,则设为2
AtomicReference<Node> head = new AtomicReference<>();
AtomicLong / AtomicBoolean / AtomicIntegerArray ...CAS 的 ABA 问题:值从 A→B→A,CAS 认为没变化。
解决:用 AtomicStampedReference(带版本号)。
线程池
核心参数(ThreadPoolExecutor):
new ThreadPoolExecutor(
2, // corePoolSize 核心线程数
5, // maximumPoolSize 最大线程数
60, TimeUnit.SECONDS, // keepAliveTime 空闲线程存活时间
new LinkedBlockingQueue<>(100), // 工作队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);执行流程:
提交任务
→ 核心线程数未满 → 创建核心线程执行
→ 核心线程满了 → 放入队列
→ 队列满了 → 创建非核心线程(不超过 max)
→ 达到 max → 执行拒绝策略
拒绝策略:
AbortPolicy:抛异常(默认)CallerRunsPolicy:由提交任务的线程自己执行DiscardPolicy:直接丢弃DiscardOldestPolicy:丢弃队列最老的任务
不要用
Executors工厂方法:newFixedThreadPool队列无界(OOM),newCachedThreadPool线程数无界(OOM)。
常用同步工具
CountDownLatch — 倒计时门闩
CountDownLatch latch = new CountDownLatch(3);
// 3个子线程各自完成后
latch.countDown(); // count - 1
// 主线程等待所有子线程完成
latch.await(); // 阻塞直到 count == 0不可重置,用完即废。
CyclicBarrier — 循环屏障
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达,继续执行");
});
// 每个线程执行完一阶段后
barrier.await(); // 等待其他线程,全部到达后同时继续可重置复用,用于分阶段并行任务。
Semaphore — 信号量
Semaphore semaphore = new Semaphore(3); // 同时最多3个线程
semaphore.acquire(); // 获取许可(-1),没有则阻塞
try {
// 访问资源
} finally {
semaphore.release(); // 释放许可(+1)
}限流、控制并发资源访问数量。
CompletableFuture
异步编排,JDK 8+:
// 异步执行,无返回值
CompletableFuture.runAsync(() -> doSomething());
// 异步执行,有返回值
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "result");
// 串行:前一步完成后执行
future.thenApply(r -> r.toUpperCase())
.thenAccept(r -> System.out.println(r));
// 并行:两个任务都完成后
CompletableFuture.allOf(f1, f2).join();
// 任意一个完成
CompletableFuture.anyOf(f1, f2).thenAccept(...);
// 异常处理
future.exceptionally(ex -> "default");并发容器
| 容器 | 特点 |
|---|---|
ConcurrentHashMap | 分段锁(JDK7)/ CAS+synchronized(JDK8),高并发HashMap |
CopyOnWriteArrayList | 写时复制,读多写少场景 |
LinkedBlockingQueue | 阻塞队列,生产者-消费者模型 |
ConcurrentLinkedQueue | 无锁非阻塞队列,CAS 实现 |
ConcurrentHashMap JDK8 原理:
- 数组 + 链表 + 红黑树(链表长度 > 8 转树)
- 写操作:对单个桶(数组槽)加 synchronized
- 读操作:无锁(
volatile保证可见性) - 扩容:多线程协助扩容
ThreadLocal
每个线程有自己独立的副本,线程间隔离:
ThreadLocal<String> local = new ThreadLocal<>();
local.set("线程A的数据");
local.get(); // "线程A的数据"
local.remove(); // 用完必须清理,否则线程池场景下内存泄漏线程池中线程复用,不 remove 会导致上一个任务的数据污染下一个任务。
InheritableThreadLocal:子线程可以继承父线程的值。