Java 线程是并发编程的核心,允许程序同时执行多个任务。虚拟线程相关内容

线程概述

线程(Thread)是操作系统能够进行运算调度的最小单位。在 Java 中,线程由 JVM 管理为轻量级执行单元:多个线程共享同一进程的内存空间(堆、方法区),但各有独立的栈空间(局部变量、方法调用栈)。Java 线程基于操作系统原生线程实现,通过 java.lang.Threadjava.lang.Runnable 提供支持。

线程的创建

继承 Thread

继承 Thread 并重写 run()。简单但不推荐——Java 单继承,会限制类的扩展性。

1
2
3
4
5
6
7
8
9
10
11
12
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
}

public class Main {
public static void main(String[] args) {
new MyThread().start();
}
}

实现 Runnable 接口

更灵活、推荐——允许类继承其他类,且便于资源共享。

1
2
3
4
5
6
7
8
9
10
11
12
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
}

public class Main {
public static void main(String[] args) {
new Thread(new MyRunnable()).start();
}
}

Callable + Future

允许线程返回结果或抛出异常,通常与 ExecutorService 结合,适合需要返回值或异常处理的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.*;

class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Task completed by " + Thread.currentThread().getName();
}
}

public class Main {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // 阻塞获取结果
executor.shutdown();
}
}

线程池(Executor Framework)

java.util.concurrent 提供线程池机制,避免频繁创建/销毁线程的开销。

1
2
3
4
5
6
7
8
9
10
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.submit(() -> System.out.println("Task in thread pool"));
executor.shutdown();
}
}

Executors 快捷工厂及隐患

工厂方法核心/最大线程队列隐患
newFixedThreadPool(n)n / n无界 LinkedBlockingQueue任务无限堆积 → OOM
newCachedThreadPool()0 / Integer.MAX_VALUESynchronousQueue突发流量创建大量线程 → 资源枯竭
newSingleThreadExecutor()1 / 1无界 LinkedBlockingQueue任务堆积放大延迟
newScheduledThreadPool(n)n / n延迟队列长任务阻塞调度线程

生产建议:尽量自定义 ThreadPoolExecutor + 有界队列 + 拒绝策略形成背压,而非依赖 Executors 快捷工厂。

自定义 ThreadPoolExecutor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class PooledExecutors {
// 命名线程工厂,便于排障
static class NamedThreadFactory implements ThreadFactory {
private final String prefix;
private final AtomicInteger idx = new AtomicInteger(1);
NamedThreadFactory(String prefix) { this.prefix = prefix; }
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, prefix + "-" + idx.getAndIncrement());
t.setUncaughtExceptionHandler((thr, ex) ->
System.err.println(thr.getName() + " uncaught: " + ex.getMessage()));
return t;
}
}

public static ExecutorService buildBoundedPool() {
int cores = Math.max(2, Runtime.getRuntime().availableProcessors());
int max = cores * 2 + 1;
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1024); // 有界队列形成背压
RejectedExecutionHandler reject = new ThreadPoolExecutor.CallerRunsPolicy(); // 调用方回退,天然限流
ThreadPoolExecutor pool = new ThreadPoolExecutor(
cores, max, 30L, TimeUnit.SECONDS, queue,
new NamedThreadFactory("biz-pool"), reject);
pool.allowCoreThreadTimeOut(true); // 允许核心线程超时回收
return pool;
}
}

构造参数

1
2
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
  • corePoolSize:基线线程数。低于此值时优先创建新线程;allowCoreThreadTimeOut(true) 后核心线程也会空闲退出。
  • maximumPoolSize:线程上限。队列无法接收且有界时才扩容到此值。注意:用无界队列时 maximumPoolSize 实际无效。
  • keepAliveTime + unit:非核心线程空闲存活时间;开启 allowCoreThreadTimeOut 后也作用于核心线程。
  • workQueue:决定伸缩与拒绝行为的关键。
    • SynchronousQueue:零容量直接移交,易扩到 maximumPoolSize,适合短任务+高并发但需强限流。
    • ArrayBlockingQueue(cap):有界,配合 CallerRunsPolicy 形成自然背压。
    • LinkedBlockingQueue(cap):传 cap 为有界;不传近似无界(不建议生产)。
    • PriorityBlockingQueue:按优先级出队,任务需实现 Comparable。
  • threadFactory:自定义线程名、优先级、守护属性与 UncaughtExceptionHandler,便于观测排障。
  • handler:触发于"线程达 max 且队列满"或执行器已 shutdown。
    • AbortPolicy(默认,抛异常)、CallerRunsPolicy(背压)、DiscardPolicy(丢弃)、DiscardOldestPolicy(丢最老);可自定义告警/降级。

任务接纳流程:运行线程数 < core → 新建线程 → 否则入队 → 入队失败且 < max → 新建线程 → 否则触发拒绝策略。

拒绝策略选择:AbortPolicy(显式失败)、CallerRunsPolicy(背压)、DiscardPolicy、DiscardOldestPolicy。

线程数参考:CPU 密集 ≈ 核心数;IO 密集可高于核心数,但需观测上下文切换与资源句柄。

常用调优/监控方法allowCoreThreadTimeOut(true)prestartAllCoreThreads()setRejectedExecutionHandler(...)getPoolSize()/getActiveCount()/getQueue().size()/getCompletedTaskCount()

优雅关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.concurrent.*;

public class ShutdownSample {
public static void gracefulShutdown(ExecutorService pool) {
pool.shutdown(); // 拒绝新任务,执行已入队任务
try {
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
pool.shutdownNow(); // 中断正在执行的任务并清空队列
if (!pool.awaitTermination(30, TimeUnit.SECONDS)) {
System.err.println("Pool did not terminate");
}
}
} catch (InterruptedException e) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}

在容器/服务停止钩子中调用优雅关闭,避免任务"半途而废"。监控接入 Micrometer/JMX,结合告警阈值动态调参。

ScheduledExecutor 定时语义

  • scheduleAtFixedRate(initialDelay, period, unit):固定频率,任务超 period 时按最小间隔串行继续。
  • scheduleWithFixedDelay(initialDelay, delay, unit):从上次任务完成时刻起算下一次。
1
2
3
ScheduledExecutorService ses = Executors.newScheduledThreadPool(2);
ses.scheduleAtFixedRate(() -> {/* metrics */}, 1, 1, TimeUnit.SECONDS);
ses.scheduleWithFixedDelay(() -> {/* health-check */}, 0, 5, TimeUnit.SECONDS);

定时任务应短小无阻塞;长任务用独立线程池或拆分。

虚拟线程

Executors.newVirtualThreadPerTaskExecutor() 适合大量阻塞型任务(如阻塞 IO),以低成本挂起/恢复。

1
2
3
4
5
6
try (ExecutorService vexec = Executors.newVirtualThreadPerTaskExecutor()) {
vexec.submit(() -> {
Thread.sleep(100); // 阻塞 IO:虚拟线程可廉价挂起
return "ok";
});
}
  • CPU 密集任务不会因虚拟线程增多而更快。
  • 避免长期持有同步器/本地代码调用造成 carrier pinning。
  • 仍需背压策略与并发上限(如信号量)防止外部系统被打爆。

线程的生命周期

Java 线程有六种状态(定义在 Thread.State 枚举):

  1. NEW:对象创建但未调用 start()
  2. RUNNABLE:已调用 start(),可能运行中或等待 CPU 调度。
  3. BLOCKED:等待获取锁(如 synchronized 块)。
  4. WAITING:通过 wait()join()LockSupport.park() 进入无限期等待。
  5. TIMED_WAITING:通过 sleep(long)wait(long)join(long) 进入有限期等待。
  6. TERMINATED:执行完成或异常终止。
1
NEW → RUNNABLE → (BLOCKED / WAITING / TIMED_WAITING) → TERMINATED

thread life cycle

线程的常用方法

  • start():启动线程,调用 run()
  • run():线程执行逻辑,需重写。
  • sleep(long millis):休眠指定时间,不释放锁
  • yield():让出 CPU,线程仍为 RUNNABLE。
  • join():等待线程执行完成。
  • interrupt():中断线程(设置中断标志,可能抛 InterruptedException)。
  • isAlive():检查线程是否存活。
  • setPriority(int):设置优先级(1-10,默认 5)。
  • setDaemon(boolean):设为守护线程(随主线程结束而结束)。

线程同步

多线程访问共享资源可能导致数据不一致,Java 提供多种同步机制。

synchronized 关键字

基于对象监视器(Monitor)实现互斥与可见性。每个对象天生携带一个监视器,与对象头 Mark Word 关联。JVM 根据竞争态在锁形态间转换(无锁 → 轻量级自旋 → 重量级阻塞);JDK 15 起移除了偏向锁。

  • 字节码:同步块编译为 monitorenter/monitorexit 指令对;同步方法通过 ACC_SYNCHRONIZED 标志由 JVM 隐式进入/退出。
  • 两个队列EntryList(竞争监视器的线程) 与 WaitSet(调用 wait 后挂起的线程);notify/notifyAll 仅将线程从 WaitSet 迁回 EntryList,恢复运行仍需重新竞争监视器。
  • 可重入:同一线程可多次进入同一监视器,JVM 维护持有计数,退出时成对递减。
  • 内存语义:退出监视器=发布(release),进入监视器=获取(acquire),形成 happens-before——随后获取的线程能看到临界区内最新写入。
  • 中断语义:竞争进入监视器的阻塞不可被中断;wait() 可被中断并抛 InterruptedException
  • 优化:自旋+阻塞混合策略降低内核态切换;JIT 可能锁粗化与锁消除。

锁粒度

1
2
3
4
5
// 实例锁,等价 synchronized(this) { ... }
public synchronized void method() { /* ... */ }

// 类锁,等价 synchronized(ClassName.class) { ... }
public static synchronized void staticMethod() { /* ... */ }

不同实例各有监视器互不干扰;类锁在整个 JVM 中对该类唯一。

可重入示例

1
2
3
4
public synchronized void outer() {
inner(); // 同一线程重入,不会死锁
}
public synchronized void inner() { /* ... */ }

正确使用 wait/notify

wait/notify/notifyAll 必须在持有同一监视器的 synchronized 块内调用,并使用 while 循环检查条件以防虚假唤醒(spurious wakeup)与丢信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
final Object lock = new Object();
boolean ready = false;

public void awaitReady() throws InterruptedException {
synchronized (lock) {
while (!ready) { // while 而非 if
lock.wait(); // 释放监视器并等待
}
// 条件满足后的逻辑
}
}

public void signalReady() {
synchronized (lock) {
ready = true;
lock.notifyAll(); // 多条件/多等待者优先 notifyAll
}
}
  • notify 只唤醒一个,可能"唤错对象";复杂场景优先 notifyAll 或拆分多个 Condition。
  • 避免"先通知后等待"丢信号:用共享条件变量 + while 检查。
  • 中断处理:wait()InterruptedException 后应恢复中断标志或按业务退出。

锁对象选择与封装

锁对象应为 private final避免锁定 thisClass 对象或字符串常量(会被驻留,可能与他处冲突),防止外部代码干扰。

1
2
3
4
5
private final Object lock = new Object(); // 私有、不可变
public void safeInc() {
synchronized (lock) { /* 临界区 */ }
}
// 反例:synchronized ("LOCK") {...} synchronized (SomeClass.class) {...}

同步集合的迭代规约

Collections.synchronizedXxx 遍历时仍需外部同步(在同一监视器上),否则结构性变化不安全:

1
2
3
4
5
List<Integer> list = Collections.synchronizedList(new ArrayList<>());
list.add(1);
synchronized (list) {
for (Integer v : list) { /* ... */ }
}

安全发布与可见性

安全发布(Safe Publication)方式:静态初始化/不可变对象、volatile 字段、synchronized 的发布与访问。

  • 何时选 volatile:变量独立、无复合不变式、只需可见性与有序性、不需要原子复合操作。
  • 何时选 synchronized:存在复合操作或不变式,需互斥+可见性同时保证。

性能、死锁与取舍

  • 最小化临界区,避免其中执行 I/O、RPC、睡眠。
  • 死锁规避:多锁按统一顺序获取,跨组件制定顺序协议;保持临界区短小、减少嵌套。
  • synchronized 不支持超时/可中断获取,需转向 Lock.tryLock(...)
  • 优先块级 synchronized(仅包裹必要共享状态),避免方法级锁过度串行化。
  • 高并发吞吐需求优先选择高层并发结构(ConcurrentHashMapBlockingQueue)。

Lock 接口(ReentrantLock / ReadWrite / Stamped)

java.util.concurrent.locks.Locksynchronized 更丰富:可中断加锁、超时获取、公平性、多条件队列、多种锁形态。最常用 ReentrantLock必须用 try/finally 释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;

public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须在 finally 中释放
}
}
}
  • 非阻塞尝试lock.tryLock() 立即返回,不可用走降级/排队/丢弃。
  • 可中断lock.lockInterruptibly() 响应中断,避免无限等待。
  • 超时获取lock.tryLock(200, TimeUnit.MILLISECONDS) 限时内未获取则放弃。
  • 公平锁new ReentrantLock(true) 按等待顺序获取,避免饥饿(吞吐略降)。
  • 状态查询(仅监控/调试,勿做强逻辑):isLocked()isHeldByCurrentThread()getHoldCount()hasQueuedThreads()getQueueLength()

死锁规避(超时 + 退避重试)

1
2
3
4
5
6
7
8
9
10
11
12
13
public void doWithTwoLocks() throws InterruptedException {
while (true) {
if (lockA.tryLock(50, TimeUnit.MILLISECONDS)) {
try {
if (lockB.tryLock(50, TimeUnit.MILLISECONDS)) {
try { /* 临界区 */ return; }
finally { lockB.unlock(); }
}
} finally { lockA.unlock(); }
}
TimeUnit.MILLISECONDS.sleep(ThreadLocalRandom.current().nextInt(5, 25)); // 随机退避降活锁
}
}

与 synchronized 差异:互斥/可见性等价;Lock 支持公平、可中断、超时、多条件队列;需手动释放(必须 try/finally),错误释放更易出错。

ReentrantReadWriteLock(读写锁)

读多写少场景提升吞吐:多读并行,写独占。不支持升级(读→写易死锁,需释放读锁再尝试写锁)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class RwCache<K, V> {
private final Map<K, V> map = new HashMap<>();
private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private final Lock r = rw.readLock();
private final Lock w = rw.writeLock();

public V get(K key) {
r.lock();
try { return map.get(key); } finally { r.unlock(); }
}

public void put(K key, V value) {
w.lock();
try { map.put(key, value); } finally { w.unlock(); }
}

// 降级: 持有写锁期间获取读锁,再释放写锁
public V putAndRead(K key, V value) {
w.lock();
try { map.put(key, value); r.lock(); }
finally { w.unlock(); }
try { return map.get(key); } finally { r.unlock(); }
}
}

StampedLock(乐观读)

相较读写锁提供乐观读以提升读性能;不重入,且不支持 lockInterruptibly,谨慎使用。严禁同线程重复加同类锁,否则死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Point {
private double x, y;
private final StampedLock sl = new StampedLock();

// 乐观读: 先无锁读取,再验证,无效则退化为悲观读
public double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
double cx = x, cy = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try { cx = x; cy = y; } finally { sl.unlockRead(stamp); }
}
return Math.hypot(cx, cy);
}

public void move(double dx, double dy) {
long stamp = sl.writeLock();
try { x += dx; y += dy; } finally { sl.unlockWrite(stamp); }
}

// 尝试读->写转换
public void moveIfAt(double ox, double oy, double nx, double ny) {
long stamp = sl.readLock();
try {
while (x == ox && y == oy) {
long ws = sl.tryConvertToWriteLock(stamp);
if (ws != 0L) { stamp = ws; x = nx; y = ny; return; }
else { sl.unlockRead(stamp); stamp = sl.writeLock(); }
}
} finally { sl.unlock(stamp); }
}
}

volatile 关键字

确保变量的可见性、防止指令重排序,但不保证原子性。典型用于双重检查单例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private static volatile Singleton instance;

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

原子类(AtomicXxx)

原子类提供无锁(非阻塞)的原子性读改写能力,底层依赖硬件 CAS 指令与内存屏障,用于计数器、状态位切换、引用替换等场景。

CAS 与内存语义

CAS(Compare-And-Swap):期望值等于当前值时原子更新,返回是否成功。高并发下常配合自旋重试,避免线程阻塞/唤醒开销。注意 ABA 问题(A→B→A 往返无法被单纯 CAS 发现),需带戳/带标记引用类规避。

1
2
3
4
5
6
7
8
9
10
11
public class CasAdder {
private final AtomicInteger value = new AtomicInteger(0);

public void add(int delta) {
int prev, next;
do {
prev = value.get();
next = prev + delta;
} while (!value.compareAndSet(prev, next)); // 失败重试(自旋)
}
}

方法语义get()/set() 等价 volatile 读/写;lazySet() 更弱发布(最终一致,低开销);compareAndSet 成功时全栅栏;getAndSet/getAndAdd/incrementAndGet 等为读改写复合原子操作;getAndUpdate(fn)/accumulateAndGet(x, acc) 用函数式更新避免手写自旋。

常见原子类族

类族适用场景
AtomicBoolean/Integer/Long计数器、开关、序列号等单变量原子更新
LongAdder/DoubleAdder高争用累加,分段分箱(Striped64)降热点,远优于 AtomicLong
LongAccumulator/DoubleAccumulator自定义聚合函数的累加器
AtomicReference<T>原子替换引用,常与不可变对象搭配避免"半更新"
AtomicStampedReference/AtomicMarkableReference带戳/带标记,抗 ABA
AtomicIntegerArray/AtomicLongArray/AtomicReferenceArray数组下标原子操作
AtomicIntegerFieldUpdater/LongFieldUpdater/ReferenceFieldUpdater反射驱动字段更新器,字段须为 volatile 非 final
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// LongAdder 计量(注意 sum() 非线性化快照,适合统计)
LongAdder adder = new LongAdder();
adder.increment();
long qps = adder.sumThenReset(); // 取值后清零

// AtomicReference + 不可变配置
AtomicReference<Config> holder = new AtomicReference<>(new Config(3000, true));
holder.updateAndGet(cfg -> cfg.withTimeout(5000));

// AtomicStampedReference 抗 ABA
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
int[] stamp = new int[1];
Integer cur = ref.get(stamp);
ref.compareAndSet(cur, 101, stamp[0], stamp[0] + 1);

// 函数式求最大值
AtomicInteger max = new AtomicInteger(Integer.MIN_VALUE);
max.accumulateAndGet(candidate, Math::max);

选型与注意

  • 计数选型:单核/低争用 → AtomicLong;多核/高争用 → LongAdder;需精确线性化读快照 → AtomicLong
  • 不要将"原子类 + 普通字段"混用形成跨变量不变式;遇到不变式引入锁或更高层抽象。
  • 跨多变量事务性要求(如"余额+库存"同时扣减)用锁/STM/数据库事务;阻塞等待场景用 Lock+Condition 或 Semaphore/CountDownLatch/Phaser。

线程安全集合

并发场景优先选内置并发集合而非手写锁,三大方案:

  • 同步包装器(阻塞)Collections.synchronizedXxx
  • 无锁/细粒度锁并发集合ConcurrentHashMapConcurrentLinkedQueue/DequeConcurrentSkipListMap/Set
  • 阻塞/可转移队列BlockingQueueTransferQueue

同步包装器

Collections.synchronizedList(new ArrayList<>()) 等对每次访问用同一把内置锁,简单但高并发吞吐低,遍历需外部加锁。适合并发度不高、旧代码快速加固。

CopyOnWriteArrayList / CopyOnWriteArraySet

写时复制:每次写操作复制底层数组,写代价高、读无需加锁且遍历为快照。适合读多写少、元素少的场景(监听器/订阅者列表、白名单)。迭代器不抛 CME 且不反映迭代期间后续写入,但不支持 remove()。不适合大集合或高写频。

ConcurrentLinkedQueue / Deque

无界、无锁(基于 CAS)的多生产者多消费者队列,操作非阻塞,适合高吞吐"邮箱/缓冲"。迭代弱一致不抛 CME;size() 为 O(n) 近似值,避免热路径调用。

BlockingQueue / TransferQueue

阻塞 put/take 与超时 offer/poll,适合生产者-消费者。常见实现:

实现特点
ArrayBlockingQueue有界数组环形队列,内存本地性好,支持公平
LinkedBlockingQueue链表,容量可选(默认近似无界),吞吐稳定
SynchronousQueue容量 0 直接移交,生产消费必须配对(Cached 线程池)
PriorityBlockingQueue基于堆的无界优先队列
DelayQueue元素实现 Delayed,到期后可出队
LinkedTransferQueue支持 transfer 语义,有消费者时直接交付
1
2
3
4
// 直接交付
LinkedTransferQueue<String> tq = new LinkedTransferQueue<>();
new Thread(() -> { try { tq.transfer("event"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();
new Thread(() -> { try { System.out.println("got: " + tq.take()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }).start();

ConcurrentHashMap / ConcurrentSkipListMap

ConcurrentHashMap(无序):禁止 null key/value;迭代弱一致不抛 CME;size() 仅近似,可用 mappingCount()

单 key 原子复合操作用 putIfAbsent/compute/computeIfAbsent/merge/replace(key,old,new) 替代"get-then-put"竞态。映射函数应短小无阻塞,勿在其中做结构性修改。批量并行操作 forEach/reduce*/search* 阈值决定是否并行。

1
2
3
4
5
6
7
8
9
10
// 并发多值 Map
ConcurrentHashMap<K, List<V>> map = new ConcurrentHashMap<>();
map.computeIfAbsent(k, kk -> Collections.synchronizedList(new ArrayList<>())).add(v);

// 热点计数优先 LongAdder
ConcurrentHashMap<K, LongAdder> counters = new ConcurrentHashMap<>();
counters.computeIfAbsent(k, kk -> new LongAdder()).increment();

// 原子更新替代竞态
map.merge(key, 1, Integer::sum);

ConcurrentSkipListMap/Set(有序/可导航):基于跳表,提供 subMap/headMap/tailMapceiling/floor/higher/lower 等导航能力。读多写少且需排序/范围查询时优势明显,代价是写入更慢、内存开销更高。

选型对照

需求推荐
列表读多写少、订阅/监听器CopyOnWriteArrayList
高吞吐非阻塞邮箱/缓冲ConcurrentLinkedQueue/Deque
生产者-消费者(有界平滑)ArrayBlockingQueue
生产者-消费者(吞吐弹性)LinkedBlockingQueue
直接交付/主动交付SynchronousQueue / LinkedTransferQueue
并发字典(无序高吞吐)ConcurrentHashMap
并发字典(有序/范围查询)ConcurrentSkipListMap
旧容器快速加固Collections.synchronizedXxx(遍历需外部加锁)

并发容器 size() 常为近似或代价高,避免在热路径依赖 size 做控制逻辑。

线程通信

线程通信用于协调进度、传递数据与信号。正确原语既要保证可见性/有序性,也要提供合适的阻塞/唤醒/取消语义。

happens-before 要点:同一监视器释放 hb 随后获取;BlockingQueue put hb 相应 take;CountDownLatch 计数到 0 hb 所有 await 返回;CyclicBarrier/Phaser 阶段推进形成屏障;CompletableFuture 完成 hb 依赖阶段开始。多数阻塞方法可中断,抛 InterruptedException 后应退出或恢复中断标志。

Object.wait/notify

依托对象监视器,wait 释放监视器进入等待队列,被唤醒后需重新竞争。模式见上文synchronized 的 wait/notify。带超时等待可用 wait(timeoutMs) 配合时间预算循环。不适合复杂条件编排与精细中断/超时控制,转用 Condition。

LockSupport.park/unpark

单个"许可"语义:unpark 可先发生,持有许可时下一次 park 立即返回,不累加多许可。无需持有锁,常用于构建更高层同步器。仅传递"继续执行"信号,不附带条件检查与可见性保障,需与共享状态(volatile/锁)配合。

1
2
LockSupport.park();      // 等待许可
LockSupport.unpark(t); // 可先于 park 调用

Lock + Condition

一个 Lock 可派生多个 Condition,分离不同等待理由、减少无关唤醒,支持 awaitNanos/awaitUntil 带时钟等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class BoundedBuffer<T> {
private final Lock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();
private final Deque<T> deque = new ArrayDeque<>();
private final int cap;

public BoundedBuffer(int cap) { this.cap = cap; }

public void put(T x) throws InterruptedException {
lock.lock();
try {
while (deque.size() == cap) notFull.await();
deque.addLast(x);
notEmpty.signal(); // 只唤醒"取"的等待者
} finally { lock.unlock(); }
}

public T take() throws InterruptedException {
lock.lock();
try {
while (deque.isEmpty()) notEmpty.await();
T v = deque.removeFirst();
notFull.signal(); // 只唤醒"放"的等待者
return v;
} finally { lock.unlock(); }
}
}

单条件、等待者等价时可用 signal;多条件/复杂依赖优先 signalAll 或拆分 Condition。

阻塞队列(哨兵停机)

通过 put/take 安全交接,天然具备可见性与可中断阻塞。哨兵(毒丸)模式优雅停机:

1
2
3
4
BlockingQueue<Integer> q = new LinkedBlockingQueue<>();
final int PILL = Integer.MIN_VALUE;
// 消费者: q.take(); 收到 PILL 则 break
// 生产者: 数据生产完毕后 q.put(PILL);

CountDownLatch / CyclicBarrier / Phaser

同步器特点
CountDownLatch一次性闩,主线程等待若干事件完成(计数到 0),不可重置
CyclicBarrier固定参与者,每轮全员到屏障同时推进,可附加 barrierAction,可复用
Phaser参与者可动态注册/撤销,适合变更规模的多阶段任务
1
2
3
4
5
6
7
8
9
10
11
12
13
// CountDownLatch
CountDownLatch ready = new CountDownLatch(3);
for (int i = 0; i < 3; i++) new Thread(() -> { try { /* 初始化 */ } finally { ready.countDown(); } }).start();
ready.await();

// CyclicBarrier
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("phase done"));
// 各线程 barrier.await() 推进阶段

// Phaser
Phaser phaser = new Phaser(1); // 主线程注册
// 子线程 phaser.register() → arriveAndAwaitAdvance() → arriveAndDeregister()
phaser.arriveAndDeregister();

Semaphore(信号量/限流)

通过许可数限制并发度,用于有限资源访问或令牌桶限流。

1
2
3
4
5
Semaphore sem = new Semaphore(10);
if (sem.tryAcquire(100, TimeUnit.MILLISECONDS)) {
try { /* 访问受限资源 */ }
finally { sem.release(); }
} else { /* 超时降级 */ }

Exchanger(配对交换)

两个线程在交换点互换对象,适用于"双缓存"生产-消费。

CompletableFuture(异步编排)

阶段式回调链传递结果与信号,支持 anyOf/allOf、超时、取消与异常传播。

1
2
3
4
5
6
CompletableFuture<String> f =
supplyAsync(() -> "A")
.thenCombine(supplyAsync(() -> "B"), (a, b) -> a + b)
.orTimeout(500, TimeUnit.MILLISECONDS)
.exceptionally(ex -> "fallback");
System.out.println(f.join());

CPU 密集尽量用专用池;避免在 thenApply 等同步阶段执行阻塞操作,必要时用 thenApplyAsync/thenComposeAsync

通信模式选型

场景推荐
生产者-消费者(背压)有界 BlockingQueue / LinkedTransferQueue
多阶段流水线CyclicBarrier / Phaser / 多段队列串接
资源限流/并发控制Semaphore
条件协调Lock + 多 Condition / wait-notifyAll
异步请求/聚合CompletableFuture anyOf/allOf + 超时降级
自定义阻塞LockSupport.park/unpark + 明确共享状态与内存语义

常见陷阱:未持锁调用 wait/notify;if 而非 while 中 wait;notify 唤错等待者丢信号;compute/merge 内阻塞操作致串行化/死锁;忽略中断超时致线程泄漏;用近似 size 做严谨控制逻辑。

线程安全问题与解决

常见问题:数据竞争(多线程同时修改共享变量)、死锁(相互等待对方释放锁)、活锁(不断尝试但无法进展)、线程饥饿(无法获取 CPU 时间)。

解决思路:使用同步机制(synchronized/Lock);避免嵌套锁降死锁风险;用线程池管理线程;优先高层次并发工具(ConcurrentHashMap/ExecutorService)。

伪共享(False Sharing)

多核处理器中,多个线程频繁修改位于同一缓存行的不同变量时,即使变量逻辑独立,也会导致缓存行在 CPU 核心间频繁失效与同步,性能急剧下降(可能比单线程慢 10 倍+)。

原理与影响

  • 缓存行通常 64 字节(x86),是 CPU 缓存与主存交换的最小单位。
  • MESI 协议标记缓存行 M/E/S/I 状态;两个线程改同缓存行不同变量 → 缓存行反复失效重载(缓存行颠簸、总线争用)。
  • 典型场景:高并发计数器、统计数组、并发队列、线程池工作窃取。
1
2
3
4
5
6
7
8
9
10
11
12
// 无填充 - 易伪共享
static class PlainCounter {
public volatile long count1 = 0;
public volatile long count2 = 0;
}

// 填充 - 隔离缓存行
static class PaddedCounter {
public volatile long count1 = 0;
public long p1, p2, p3, p4, p5, p6, p7; // 填充至 64 字节
public volatile long count2 = 0;
}

解决方案

  1. 优先用成熟并发库LongAdder(内部 Cell 数组分片)、ConcurrentHashMap 等已优化伪共享。
  2. 手动填充(Padding):加无用字段填充缓存行;可能被 JIT 消除,需谨慎。
  3. @Contended 注解(JDK 8+):官方填充机制,需 -XX:-RestrictContended 启用,默认填充 128 字节(-XX:ContendedPaddingWidth)。仅用于 JDK 内部或性能关键路径,应用代码慎用。
1
2
3
4
5
6
7
import jdk.internal.vm.annotation.Contended; // JDK 12+; JDK 8-11 用 sun.misc.Contended

@Contended
public class ContendedCounter {
public volatile long counter1;
public volatile long counter2;
}

检测

1
2
3
# Linux perf
perf stat -e cache-misses,cache-references ./your-java-app
perf record -e cache-misses -g java YourApp && perf report

用 JMH 基准测试量化优化效果,并考虑不同 CPU 架构缓存行差异(64/128 字节)。

高级主题

Fork/Join 框架

用于分治算法,适合递归任务分解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SumTask extends RecursiveTask<Long> {
private final int[] array;
private final int start, end;

public SumTask(int[] array, int start, int end) {
this.array = array; this.start = start; this.end = end;
}

@Override
protected Long compute() {
if (end - start <= 100) {
long sum = 0;
for (int i = start; i < end; i++) sum += array[i];
return sum;
}
int mid = start + (end - start) / 2;
SumTask left = new SumTask(array, start, mid);
SumTask right = new SumTask(array, mid, end);
left.fork();
return right.compute() + left.join();
}
}

ThreadLocal

为每个线程提供独立变量副本。注意用完 remove() 防止内存泄漏(尤其线程池场景)。

1
2
private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
// 使用后: threadLocal.remove();

最佳实践与调试

  • 优先线程池,避免手动创建大量线程。
  • 最小化锁范围,临界区不执行 I/O/RPC/睡眠。
  • 避免共享状态,尽量用不可变对象或局部变量。
  • 正确处理中断:捕获 InterruptedException 并恢复中断状态。
  • 测试并发代码:用 JMeter、JCStress 测试线程安全性。

调试:死锁用 jstack 或 IDE 查看线程状态;性能用 VisualVM/JProfiler 分析瓶颈;内存泄漏检查 ThreadLocal 是否清理。

总结

Java 通过 Thread/Runnable/Executor 提供并发支持。合理使用同步机制(synchronized/Lock/volatile)、线程池与并发工具,可有效管理多线程复杂性;Fork/JoinCompletableFuture 等进一步提升灵活性。核心原则:优先高层并发结构而非手写锁,最小化共享与临界区,统一中断与停机协议