JUC

返回 Java 基础 → 相关:对象(总览)· JMM(hb、volatile、安全发布 — 先理解规范再看 API)· JVM(运行时内存结构、对象头锁状态)· 反射(动态代理底层)

java.util.concurrent — Java 并发工具包。

对象主题边界:本文讲如何用锁、waitvolatile 操作堆上实例;对象头位域、分配与回收见 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
RUNNABLEstart() 后,包含就绪和运行中
BLOCKED等待 synchronized 锁
WAITINGwait() / join() / LockSupport.park()
TIMED_WAITINGsleep(n) / wait(n)
TERMINATEDrun() 结束

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+)
重量级锁自旋失败、多线程阻塞、waitObjectMonitor;未抢到锁的线程在 OS 层阻塞

升级触发(简化)

  1. 偏向锁:别的线程来抢 → 偏向撤销(Revoke,可能有 STW)→ 往往进入轻量级或竞争路径。高并发常 -XX:-UseBiasedLocking(JDK 8~14),见 线程与并发参数
  2. 轻量级锁:CAS 失败或自旋耗尽 → 膨胀为重量级。
  3. 重量级锁wait() 释放 Monitor 并挂起;可见性见 synchronized(与轻量/重量无关)。

与 JIT、JUC 其它锁的区别

机制是否走 Mark Word 锁升级
synchronized✅ 是
ReentrantLock / AQS❌ 否,Java 层 volatile + CAS + 队列
锁消除 / 锁粗化(JIT)编译期去掉或合并 synchronized,见 JIT

实践提示

  • 低竞争、短临界区:轻量级 + 自旋通常够用。
  • 高竞争:尽快变重量级,应缩小锁粒度或改用 JUC 无锁/队列结构。
  • 勿在 synchronizedsleep 占锁 → Thread.sleep() 与 Object.wait() 的区别

synchronized 与 Monitor(对象头)

synchronized 锁的是某个对象实例(或 Class),字节码为 monitorenter / monitorexit,与 Monitor 绑定;状态记在 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()唤醒 一个同一 objwait 的线程(具体哪个由 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。本文只列工程用法。

作用

  1. 可见性:写对后续读建立 happens-before
  2. 禁止部分重排序(如 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

synchronizedReentrantLock
锁类型非公平公平/非公平可选
可中断lockInterruptibly()
超时获取tryLock(timeout)
条件变量wait/notifyCondition
自动释放❌ 需手动 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:子线程可以继承父线程的值。


相关链接