一、前言
本文将继续深入Java多线程的高级内容,包括线程池原理与实战、volatile与synchronized底层机制、CAS原子操作、以及JUC并发工具类的使用。通过大量代码示例和原理图解,帮助大家构建完整的多线程知识体系。
二、线程池详解
2.1 为什么要使用线程池
线程池是管理线程的容器,可以避免频繁创建和销毁线程的开销。在上一篇的性能测试中,我们已经看到:直接创建1000个线程耗时1567ms,而使用线程池仅需234ms,性能提升约6.7倍。
线程池的核心优势:
- 降低资源消耗:重复利用已创建的线程,减少创建和销毁的开销
- 提高响应速度:任务到达时,无需等待线程创建即可执行
- 便于线程管理:统一分配、调优和监控
- 控制并发数量:防止系统因过多线程而崩溃
2.2 ThreadPoolExecutor核心参数
java
/**
* ThreadPoolExecutor完整构造方法
*/
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务等待队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
参数详解:
| 参数 | 说明 | 注意事项 |
|---|---|---|
| corePoolSize | 核心线程数,即使空闲也保留 | 根据CPU核数和任务类型设置 |
| maximumPoolSize | 最大线程数 | 一般设置为corePoolSize的2倍 |
| keepAliveTime | 非核心线程空闲存活时间 | 超过此时间无任务则回收 |
| workQueue | 任务等待队列 | 常用LinkedBlockingQueue、ArrayBlockingQueue |
| threadFactory | 创建线程的工厂 | 可自定义线程名称、优先级等 |
| handler | 拒绝策略 | 队列满且线程数达上限时触发 |
2.3 线程池执行流程
java
import java.util.concurrent.*;
/**
* 线程池执行流程演示
*/
public class ThreadPoolExecuteFlow {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
4, // 最大线程数
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new LinkedBlockingQueue<>(4), // 容量为4的队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 提交8个任务,观察线程池的变化
for (int i = 0; i < 8; i++) {
final int taskNum = i;
executor.execute(() -> {
System.out.println("任务" + taskNum + "由 " +
Thread.currentThread().getName() + " 执行");
try {
Thread.sleep(2000); // 模拟任务执行2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 打印线程池状态
System.out.println("提交任务" + taskNum + "后 => 活跃线程: " +
executor.getActiveCount() + ", 队列大小: " + executor.getQueue().size() +
", 已完成: " + executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
运行结果分析:
提交任务0后 => 活跃线程: 1, 队列大小: 0, 已完成: 0
任务0由 pool-1-thread-1 执行
提交任务1后 => 活跃线程: 2, 队列大小: 0, 已完成: 0
任务1由 pool-1-thread-2 执行
提交任务2后 => 活跃线程: 2, 队列大小: 1, 已完成: 0
提交任务3后 => 活跃线程: 2, 队列大小: 2, 已完成: 0
提交任务4后 => 活跃线程: 2, 队列大小: 3, 已完成: 0
提交任务5后 => 活跃线程: 2, 队列大小: 4, 已完成: 0
提交任务6后 => 活跃线程: 3, 队列大小: 4, 已完成: 0 <-- 创建非核心线程
任务6由 pool-1-thread-3 执行
提交任务7后 => 活跃线程: 4, 队列大小: 4, 已完成: 0 <-- 创建非核心线程
任务7由 pool-1-thread-4 执行
执行流程总结:
- 任务提交:调用execute()提交任务
- 核心线程检查:当前线程数 < corePoolSize,创建新线程执行任务
- 队列检查:当前线程数 ≥ corePoolSize,任务进入workQueue等待
- 最大线程检查:队列已满,且当前线程数 < maximumPoolSize,创建非核心线程
- 拒绝策略:队列已满,且当前线程数 ≥ maximumPoolSize,执行拒绝策略

2.4 四种拒绝策略详解
java
/**
* 线程池拒绝策略测试
*/
public class RejectedPolicyTest {
public static void main(String[] args) {
// 1. AbortPolicy - 直接抛出异常(默认)
testPolicy("AbortPolicy", new ThreadPoolExecutor.AbortPolicy());
// 2. CallerRunsPolicy - 由调用线程执行
testPolicy("CallerRunsPolicy", new ThreadPoolExecutor.CallerRunsPolicy());
// 3. DiscardPolicy - 静默丢弃
testPolicy("DiscardPolicy", new ThreadPoolExecutor.DiscardPolicy());
// 4. DiscardOldestPolicy - 丢弃最老任务
testPolicy("DiscardOldestPolicy", new ThreadPoolExecutor.DiscardOldestPolicy());
}
private static void testPolicy(String name, RejectedExecutionHandler handler) {
System.out.println("
========== " + name + " ==========");
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1),
handler
);
for (int i = 0; i < 5; i++) {
final int taskNum = i;
try {
executor.execute(() -> {
System.out.println("执行任务 " + taskNum);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
});
System.out.println("任务 " + taskNum + " 提交成功");
} catch (Exception e) {
System.out.println("任务 " + taskNum + " 提交失败: " + e.getClass().getSimpleName());
}
}
executor.shutdown();
}
}
运行结果:
========== AbortPolicy ==========
任务 0 提交成功
任务 1 提交成功
任务 2 提交失败: RejectedExecutionException
任务 3 提交失败: RejectedExecutionException
任务 4 提交失败: RejectedExecutionException
========== CallerRunsPolicy ==========
任务 0 提交成功
任务 1 提交成功
执行任务 2 <-- 主线程执行
任务 2 提交成功
执行任务 3 <-- 主线程执行
任务 3 提交成功
执行任务 4 <-- 主线程执行
任务 4 提交成功
========== DiscardPolicy ==========
任务 0 提交成功
任务 1 提交成功
任务 2 提交成功 <-- 被静默丢弃,无输出
任务 3 提交成功 <-- 被静默丢弃,无输出
任务 4 提交成功 <-- 被静默丢弃,无输出
========== DiscardOldestPolicy ==========
任务 0 提交成功
任务 1 提交成功
任务 2 提交成功 <-- 丢弃任务0,执行任务2
任务 3 提交成功 <-- 丢弃任务1,执行任务3
任务 4 提交成功 <-- 丢弃任务2,执行任务4
| 拒绝策略 | 特点 | 适用场景 |
|---|---|---|
| AbortPolicy | 抛出异常,阻止系统过载 | 核心业务,不能丢任务 |
| CallerRunsPolicy | 降低提交速度,提供缓冲 | 允许延迟执行 |
| DiscardPolicy | 静默丢弃,不处理 | 非关键任务,可容忍丢失 |
| DiscardOldestPolicy | 丢弃最老任务,尝试新任务 | 优先处理新请求 |
2.5 线程池监控与调优
java
/**
* 线程池监控示例
*/
public class ThreadPoolMonitor {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10)
);
// 定时打印线程池状态
ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);
monitor.scheduleAtFixedRate(() -> {
System.out.println("========================================");
System.out.println("核心线程数: " + executor.getCorePoolSize());
System.out.println("最大线程数: " + executor.getMaximumPoolSize());
System.out.println("当前线程数: " + executor.getPoolSize());
System.out.println("活跃线程数: " + executor.getActiveCount());
System.out.println("队列大小: " + executor.getQueue().size());
System.out.println("已完成任务: " + executor.getCompletedTaskCount());
System.out.println("总任务数: " + executor.getTaskCount());
System.out.println("========================================
");
}, 0, 1, TimeUnit.SECONDS);
// 提交20个任务
for (int i = 0; i < 20; i++) {
final int taskNum = i;
executor.execute(() -> {
System.out.println("【执行任务 " + taskNum + "】");
try { Thread.sleep(3000); } catch (InterruptedException e) {}
});
Thread.sleep(200);
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
monitor.shutdown();
}
}
线程池调优建议:
| 场景 | corePoolSize | maximumPoolSize | workQueue |
|---|---|---|---|
| CPU密集型 | CPU核数+1 | CPU核数+1 | 较小队列 |
| IO密集型 | CPU核数*2 | CPU核数*4 | 较大队列 |
| 混合型 | 根据任务比例调整 | 根据任务比例调整 | 适中队列 |
三、volatile关键字深度解析
3.1 volatile保证可见性
java
/**
* volatile可见性测试
* 验证volatile变量对所有线程可见
*/
public class VolatileVisibilityTest {
// 使用volatile修饰
private static volatile boolean flag = false;
// 不使用volatile(对比测试)
private static boolean normalFlag = false;
public static void main(String[] args) throws InterruptedException {
System.out.println("========== volatile可见性测试 ==========
");
// 测试1:使用volatile
System.out.println("【测试1】使用volatile变量");
testVolatile(true);
Thread.sleep(1000);
// 测试2:不使用volatile
System.out.println("
【测试2】不使用volatile变量");
testVolatile(false);
}
private static void testVolatile(boolean useVolatile) {
if (useVolatile) {
flag = false;
} else {
normalFlag = false;
}
Thread reader = new Thread(() -> {
System.out.println("读线程启动,开始检测变量...");
int count = 0;
if (useVolatile) {
while (!flag) {
count++;
}
} else {
while (!normalFlag) {
count++;
// 注意:这里可能永远循环,因为看不到主线程的修改
if (count > 100000000) {
System.out.println("读线程循环超过1亿次仍未检测到变化,可能存在可见性问题!");
break;
}
}
}
System.out.println("读线程检测到变量变化,循环次数: " + count);
});
Thread writer = new Thread(() -> {
try {
Thread.sleep(100); // 确保读线程先启动
} catch (InterruptedException e) {}
if (useVolatile) {
flag = true;
} else {
normalFlag = true;
}
System.out.println("写线程修改变量为true");
});
reader.start();
writer.start();
try {
reader.join(5000); // 最多等待5秒
writer.join();
} catch (InterruptedException e) {}
}
}
运行结果:
========== volatile可见性测试 ==========
【测试1】使用volatile变量
读线程启动,开始检测变量...
写线程修改变量为true
读线程检测到变量变化,循环次数: 0 <-- 立即检测到
【测试2】不使用volatile变量
读线程启动,开始检测变量...
写线程修改变量为true
读线程循环超过1亿次仍未检测到变化,可能存在可见性问题! <-- 永远看不到变化
3.2 volatile保证有序性(禁止指令重排序)
java
/**
* volatile禁止指令重排序示例
* 单例模式的双重检查锁定(DCL)
*/
public class VolatileSingleton {
// 必须使用volatile,防止指令重排序导致获取未初始化的对象
private static volatile VolatileSingleton instance;
private VolatileSingleton() {
// 模拟耗时初始化
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
public static VolatileSingleton getInstance() {
if (instance == null) { // 第一次检查(无锁)
synchronized (VolatileSingleton.class) {
if (instance == null) { // 第二次检查(有锁)
instance = new VolatileSingleton(); // 关键:需要volatile
}
}
}
return instance;
}
}
为什么需要volatile?
instance = new VolatileSingleton()实际上分为三步:
- 分配内存空间
- 初始化对象
- 将引用指向内存地址
如果没有volatile,步骤2和3可能被重排序。其他线程可能获取到一个未完全初始化的对象,导致空指针异常。
3.3 volatile不保证原子性
java
/**
* volatile不保证原子性测试
*/
public class VolatileNotAtomic {
private static volatile int counter = 0;
public static void main(String[] args) throws InterruptedException {
System.out.println("========== volatile不保证原子性 ==========
");
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter++; // 非原子操作,即使volatile也无法保证
}
});
threads[i].start();
}
for (Thread t : threads) t.join();
System.out.println("最终结果: " + counter);
System.out.println("期望值: 100000");
System.out.println("是否一致: " + (counter == 100000));
if (counter != 100000) {
System.out.println("
结论:volatile不能保证原子性,需要使用synchronized或AtomicInteger");
}
}
}
运行结果:
========== volatile不保证原子性 ==========
最终结果: 98732
期望值: 100000
是否一致: false
结论:volatile不能保证原子性,需要使用synchronized或AtomicInteger
四、synchronized底层原理
4.1 synchronized的三种使用方式
java
/**
* synchronized的三种使用方式
*/
public class SynchronizedUsage {
private int count = 0;
private static int staticCount = 0;
private final Object lock = new Object();
// 1. 同步实例方法 - 锁对象是当前实例(this)
public synchronized void syncMethod() {
count++;
System.out.println("同步实例方法,count=" + count);
}
// 2. 同步静态方法 - 锁对象是类的Class对象
public static synchronized void syncStaticMethod() {
staticCount++;
System.out.println("同步静态方法,staticCount=" + staticCount);
}
// 3. 同步代码块 - 锁对象是指定的对象
public void syncBlock() {
synchronized (lock) {
count++;
System.out.println("同步代码块,count=" + count);
}
}
// 3.1 同步代码块 - 锁当前实例
public void syncThisBlock() {
synchronized (this) {
count++;
System.out.println("同步this代码块,count=" + count);
}
}
// 3.2 同步代码块 - 锁Class对象
public void syncClassBlock() {
synchronized (SynchronizedUsage.class) {
staticCount++;
System.out.println("同步Class代码块,staticCount=" + staticCount);
}
}
}
4.2 锁升级过程
synchronized在JDK6之后进行了优化,引入了锁升级机制:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
java
/**
* 锁升级过程演示
* 通过JVM参数查看锁状态:-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
*/
public class LockUpgradeDemo {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
// 阶段1:无锁状态
System.out.println("【阶段1】对象创建后,无锁状态");
printLockState(lock);
// 阶段2:偏向锁(只有一个线程访问)
System.out.println("
【阶段2】单线程访问,升级为偏向锁");
synchronized (lock) {
printLockState(lock);
}
// 阶段3:轻量级锁(多个线程交替访问)
System.out.println("
【阶段3】多个线程交替访问,升级为轻量级锁");
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取锁");
printLockState(lock);
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取锁");
printLockState(lock);
}
});
t1.start(); t1.join();
t2.start(); t2.join();
// 阶段4:重量级锁(多个线程竞争)
System.out.println("
【阶段4】多个线程同时竞争,升级为重量级锁");
Thread t3 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程3获取锁,持有2秒");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
}
});
Thread t4 = new Thread(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock) {
System.out.println("线程4获取锁");
}
});
t3.start();
t4.start();
t3.join();
t4.join();
System.out.println("
锁升级完成!");
}
// 打印对象头信息(简化版)
private static void printLockState(Object obj) {
System.out.println(" 对象哈希码: " + System.identityHashCode(obj));
System.out.println(" 对象类名: " + obj.getClass().getSimpleName());
// 实际可以通过JOL工具查看对象头
}
}
锁升级详解:
| 锁状态 | 适用场景 | 实现方式 | 性能 |
|---|---|---|---|
| 无锁 | 对象刚创建 | 无 | 最高 |
| 偏向锁 | 只有一个线程访问 | Mark Word记录线程ID | 极高(接近无锁) |
| 轻量级锁 | 多个线程交替访问 | CAS自旋 | 较高 |
| 重量级锁 | 多个线程同时竞争 | 操作系统互斥量 | 较低 |
4.3 synchronized与ReentrantLock对比
java
import java.util.concurrent.locks.ReentrantLock;
/**
* synchronized与ReentrantLock对比测试
*/
public class SyncVsLockTest {
private static int syncCount = 0;
private static int lockCount = 0;
private static final Object syncLock = new Object();
private static final ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
System.out.println("========== synchronized vs ReentrantLock ==========
");
// 测试synchronized
long syncTime = testSynchronized();
System.out.println("synchronized耗时: " + syncTime + "ms");
// 测试ReentrantLock
long lockTime = testReentrantLock();
System.out.println("ReentrantLock耗时: " + lockTime + "ms");
System.out.println("
性能比: " + (syncTime / (double)lockTime));
}
private static long testSynchronized() throws InterruptedException {
syncCount = 0;
Thread[] threads = new Thread[100];
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
synchronized (syncLock) {
syncCount++;
}
}
});
threads[i].start();
}
for (Thread t : threads) t.join();
return System.currentTimeMillis() - start;
}
private static long testReentrantLock() throws InterruptedException {
lockCount = 0;
Thread[] threads = new Thread[100];
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000; j++) {
reentrantLock.lock();
try {
lockCount++;
} finally {
reentrantLock.unlock();
}
}
});
threads[i].start();
}
for (Thread t : threads) t.join();
return System.currentTimeMillis() - start;
}
}
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 实现方式 | JVM层面,内置关键字 | API层面,Java代码实现 |
| 锁的获取 | 隐式,自动释放 | 显式,需手动unlock |
| 可重入性 | 支持 | 支持 |
| 公平性 | 非公平 | 可配置公平/非公平 |
| 中断响应 | 不可中断 | 支持lockInterruptibly() |
| 条件变量 | 一个(wait/notify) | 多个Condition |
| 性能 | JDK6后优化,接近Lock | 竞争激烈时更优 |
| 灵活性 | 低 | 高(可尝试获取、定时获取等) |
五、CAS原子操作
5.1 CAS原理
CAS(Compare-And-Swap)是一种乐观锁机制,包含三个操作数:
- V:内存位置的实际值
- E:期望值(旧值)
- N:新值
当且仅当V == E时,才将N写入内存,否则重试。
java
import java.util.concurrent.atomic.AtomicInteger;
/**
* CAS原子操作演示
*/
public class CASDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("========== CAS原子操作演示 ==========
");
AtomicInteger atomicInt = new AtomicInteger(0);
// 演示CAS操作
System.out.println("初始值: " + atomicInt.get());
// compareAndSet(期望值, 新值)
boolean success1 = atomicInt.compareAndSet(0, 1);
System.out.println("CAS(0->1): " + success1 + ", 当前值: " + atomicInt.get());
boolean success2 = atomicInt.compareAndSet(0, 2);
System.out.println("CAS(0->2): " + success2 + ", 当前值: " + atomicInt.get());
boolean success3 = atomicInt.compareAndSet(1, 2);
System.out.println("CAS(1->2): " + success3 + ", 当前值: " + atomicInt.get());
// 多线程CAS测试
System.out.println("
【多线程CAS测试】");
AtomicInteger counter = new AtomicInteger(0);
Thread[] threads = new Thread[100];
for (int i = 0; i < 100; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet(); // 内部使用CAS
}
});
threads[i].start();
}
for (Thread t : threads) t.join();
System.out.println("最终结果: " + counter.get() + " (期望: 100000)");
}
}
运行结果:
========== CAS原子操作演示 ==========
初始值: 0
CAS(0->1): true, 当前值: 1
CAS(0->2): false, 当前值: 1 <-- 期望值0不等于当前值1
CAS(1->2): true, 当前值: 2
【多线程CAS测试】
最终结果: 100000 (期望: 100000)
5.2 CAS的ABA问题
java
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* CAS的ABA问题演示与解决
*/
public class ABAProblemDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("========== ABA问题演示 ==========
");
// 演示ABA问题
demonstrateABAProblem();
Thread.sleep(1000);
// 使用AtomicStampedReference解决ABA问题
solveABAProblem();
}
private static void demonstrateABAProblem() {
System.out.println("【有ABA问题】使用AtomicInteger");
AtomicInteger atomicInt = new AtomicInteger(100);
Thread t1 = new Thread(() -> {
System.out.println("线程1: 读取值 " + atomicInt.get());
try { Thread.sleep(1000); } catch (InterruptedException e) {}
// 期望是100,但实际上已经被改过了
boolean success = atomicInt.compareAndSet(100, 200);
System.out.println("线程1: CAS(100->200) = " + success);
});
Thread t2 = new Thread(() -> {
try { Thread.sleep(500); } catch (InterruptedException e) {}
System.out.println("线程2: 修改 100->50");
atomicInt.compareAndSet(100, 50);
System.out.println("线程2: 修改 50->100");
atomicInt.compareAndSet(50, 100);
System.out.println("线程2: 值变回100,但经历ABA过程");
});
t1.start();
t2.start();
try { t1.join(); t2.join(); } catch (InterruptedException e) {}
}
private static void solveABAProblem() {
System.out.println("
【解决ABA问题】使用AtomicStampedReference");
AtomicStampedReference<Integer> stampedRef =
new AtomicStampedReference<>(100, 0);
Thread t1 = new Thread(() -> {
int[] stampHolder = new int[1];
Integer value = stampedRef.get(stampHolder);
int stamp = stampHolder[0];
System.out.println("线程1: 读取值 " + value + ", 版本 " + stamp);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
boolean success = stampedRef.compareAndSet(100, 200, stamp, stamp + 1);
System.out.println("线程1: CAS(100->200, 版本" + stamp + "->" + (stamp+1) + ") = " + success);
});
Thread t2 = new Thread(() -> {
try { Thread.sleep(500); } catch (InterruptedException e) {}
int[] stampHolder = new int[1];
stampedRef.get(stampHolder);
int stamp = stampHolder[0];
System.out.println("线程2: 修改 100->50, 版本 " + stamp + "->" + (stamp+1));
stampedRef.compareAndSet(100, 50, stamp, stamp + 1);
stampedRef.get(stampHolder);
stamp = stampHolder[0];
System.out.println("线程2: 修改 50->100, 版本 " + stamp + "->" + (stamp+1));
stampedRef.compareAndSet(50, 100, stamp, stamp + 1);
});
t1.start();
t2.start();
try { t1.join(); t2.join(); } catch (InterruptedException e) {}
System.out.println("
最终值: " + stampedRef.getReference());
}
}
运行结果:
========== ABA问题演示 ==========
【有ABA问题】使用AtomicInteger
线程1: 读取值 100
线程2: 修改 100->50
线程2: 修改 50->100
线程2: 值变回100,但经历ABA过程
线程1: CAS(100->200) = true <-- 线程1不知道值被修改过!
【解决ABA问题】使用AtomicStampedReference
线程1: 读取值 100, 版本 0
线程2: 修改 100->50, 版本 0->1
线程2: 修改 50->100, 版本 1->2
线程1: CAS(100->200, 版本0->1) = false <-- 版本不匹配,CAS失败!
最终值: 100
六、JUC并发工具类
6.1 CountDownLatch
CountDownLatch(倒计时门)用于等待一个或多个线程完成操作。
java
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatch演示
* 模拟多线程下载文件,主线程等待所有下载完成
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("========== CountDownLatch演示 ==========
");
int fileCount = 5;
CountDownLatch latch = new CountDownLatch(fileCount);
System.out.println("开始下载 " + fileCount + " 个文件...
");
for (int i = 0; i < fileCount; i++) {
final int fileNum = i + 1;
new Thread(() -> {
System.out.println("【开始】下载文件 " + fileNum);
try {
// 模拟下载时间
Thread.sleep((long)(Math.random() * 3000 + 1000));
} catch (InterruptedException e) {}
System.out.println("【完成】文件 " + fileNum + " 下载完成");
latch.countDown(); // 计数减1
}, "Download-" + fileNum).start();
}
System.out.println("主线程等待所有文件下载完成...
");
latch.await(); // 等待计数归零
System.out.println("
✅ 所有文件下载完成,主线程继续执行");
}
}
运行结果:
========== CountDownLatch演示 ==========
开始下载 5 个文件...
主线程等待所有文件下载完成...
【开始】下载文件 1
【开始】下载文件 2
【开始】下载文件 3
【开始】下载文件 4
【开始】下载文件 5
【完成】文件 3 下载完成
【完成】文件 1 下载完成
【完成】文件 5 下载完成
【完成】文件 2 下载完成
【完成】文件 4 下载完成
✅ 所有文件下载完成,主线程继续执行
6.2 CyclicBarrier
CyclicBarrier(循环屏障)用于多线程互相等待到达某个屏障点。
java
import java.util.concurrent.CyclicBarrier;
/**
* CyclicBarrier演示
* 模拟多人组队游戏,所有队员准备好后才能开始
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
System.out.println("========== CyclicBarrier演示 ==========
");
int playerCount = 4;
CyclicBarrier barrier = new CyclicBarrier(playerCount, () -> {
System.out.println("
🎮 所有队员已准备就绪,游戏开始!
");
});
for (int i = 0; i < playerCount; i++) {
final int playerNum = i + 1;
new Thread(() -> {
System.out.println("玩家 " + playerNum + " 正在准备...");
try {
// 模拟准备时间
Thread.sleep((long)(Math.random() * 3000 + 1000));
System.out.println("玩家 " + playerNum + " 准备完成,等待其他玩家...");
barrier.await(); // 到达屏障,等待其他玩家
// 所有玩家都到达后,开始游戏
System.out.println("玩家 " + playerNum + " 开始游戏!");
} catch (Exception e) {
e.printStackTrace();
}
}, "Player-" + playerNum).start();
}
}
}
运行结果:
========== CyclicBarrier演示 ==========
玩家 1 正在准备...
玩家 2 正在准备...
玩家 3 正在准备...
玩家 4 正在准备...
玩家 3 准备完成,等待其他玩家...
玩家 1 准备完成,等待其他玩家...
玩家 4 准备完成,等待其他玩家...
玩家 2 准备完成,等待其他玩家...
🎮 所有队员已准备就绪,游戏开始!
玩家 2 开始游戏!
玩家 3 开始游戏!
玩家 1 开始游戏!
玩家 4 开始游戏!
6.3 Semaphore
Semaphore(信号量)用于控制同时访问某个资源的线程数量。
java
import java.util.concurrent.Semaphore;
/**
* Semaphore演示
* 模拟数据库连接池,限制最大连接数
*/
public class SemaphoreDemo {
public static void main(String[] args) {
System.out.println("========== Semaphore演示 ==========
");
int maxConnections = 3; // 最大连接数
Semaphore semaphore = new Semaphore(maxConnections);
System.out.println("数据库连接池初始化,最大连接数: " + maxConnections + "
");
// 模拟10个客户端请求
for (int i = 0; i < 10; i++) {
final int clientNum = i + 1;
new Thread(() -> {
try {
System.out.println("【客户端 " + clientNum + "】请求连接...");
semaphore.acquire(); // 获取许可
System.out.println("【客户端 " + clientNum + "】✅ 获取连接,执行查询");
Thread.sleep(2000); // 模拟查询时间
System.out.println("【客户端 " + clientNum + "】❌ 释放连接");
semaphore.release(); // 释放许可
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Client-" + clientNum).start();
try { Thread.sleep(200); } catch (InterruptedException e) {}
}
}
}
运行结果:
========== Semaphore演示 ==========
数据库连接池初始化,最大连接数: 3
【客户端 1】请求连接...
【客户端 1】✅ 获取连接,执行查询
【客户端 2】请求连接...
【客户端 2】✅ 获取连接,执行查询
【客户端 3】请求连接...
【客户端 3】✅ 获取连接,执行查询
【客户端 4】请求连接... <-- 等待
【客户端 5】请求连接... <-- 等待
【客户端 1】❌ 释放连接
【客户端 4】✅ 获取连接,执行查询 <-- 客户端1释放后获取
【客户端 2】❌ 释放连接
【客户端 5】✅ 获取连接,执行查询 <-- 客户端2释放后获取
...
6.4 三者对比总结
| 特性 | CountDownLatch | CyclicBarrier | Semaphore |
|---|---|---|---|
| 计数方向 | 递减到0 | 递增到指定值 | 获取/释放许可 |
| 可重用 | ❌ 一次性 | ✅ 可循环使用 | ✅ 可循环使用 |
| 等待方 | 一个或多个线程等待 | 多个线程互相等待 | 获取许可时可能等待 |
| 典型场景 | 主线程等待子线程完成 | 多阶段计算、组队任务 | 资源池限流、并发控制 |
| 异常处理 | 某个线程失败不影响其他 | 某个线程失败所有等待线程抛出BrokenBarrierException | 不影响其他线程 |
七、ThreadLocal线程本地变量
7.1 ThreadLocal基本使用
java
/**
* ThreadLocal基本使用演示
*/
public class ThreadLocalDemo {
// 创建ThreadLocal变量,每个线程独立拥有副本
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
System.out.println("========== ThreadLocal演示 ==========
");
// 主线程设置值
threadLocal.set("主线程的值");
threadId.set(100);
System.out.println("主线程: threadLocal=" + threadLocal.get() + ", threadId=" + threadId.get());
// 创建子线程
Thread t1 = new Thread(() -> {
System.out.println("
线程1启动...");
System.out.println("线程1初始值: threadLocal=" + threadLocal.get() + ", threadId=" + threadId.get());
threadLocal.set("线程1的值");
threadId.set(1);
System.out.println("线程1设置后: threadLocal=" + threadLocal.get() + ", threadId=" + threadId.get());
}, "Thread-1");
Thread t2 = new Thread(() -> {
System.out.println("
线程2启动...");
System.out.println("线程2初始值: threadLocal=" + threadLocal.get() + ", threadId=" + threadId.get());
threadLocal.set("线程2的值");
threadId.set(2);
System.out.println("线程2设置后: threadLocal=" + threadLocal.get() + ", threadId=" + threadId.get());
}, "Thread-2");
t1.start();
t2.start();
try { t1.join(); t2.join(); } catch (InterruptedException e) {}
// 主线程的值不受影响
System.out.println("
主线程最终: threadLocal=" + threadLocal.get() + ", threadId=" + threadId.get());
}
}
运行结果:
========== ThreadLocal演示 ==========
主线程: threadLocal=主线程的值, threadId=100
线程1启动...
线程1初始值: threadLocal=null, threadId=0
线程1设置后: threadLocal=线程1的值, threadId=1
线程2启动...
线程2初始值: threadLocal=null, threadId=0
线程2设置后: threadLocal=线程2的值, threadId=2
主线程最终: threadLocal=主线程的值, threadId=100
7.2 ThreadLocal内存泄漏问题
java
import java.util.concurrent.ExecutorService;
import java.concurrent.Executors;
/**
* ThreadLocal内存泄漏演示
*/
public class ThreadLocalMemoryLeak {
private static ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
System.out.println("========== ThreadLocal内存泄漏演示 ==========
");
ExecutorService executor = Executors.newFixedThreadPool(2);
// 模拟100次任务,每次分配1MB内存
for (int i = 0; i < 100; i++) {
final int taskNum = i;
executor.execute(() -> {
// 分配大对象到ThreadLocal
threadLocal.set(new byte[1024 * 1024]); // 1MB
System.out.println("任务 " + taskNum + " 分配1MB内存");
// 注意:这里没有调用remove(),导致内存泄漏!
// threadLocal.remove(); // 应该加上这行
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
System.out.println("
⚠️ 注意:如果没有调用remove(),线程池中的线程会一直持有引用,导致内存无法回收!");
}
}
正确使用方法:
java
public class ThreadLocalSafeUsage {
private static ThreadLocal<Object> threadLocal = new ThreadLocal<>();
public void safeUse() {
try {
threadLocal.set(new Object());
// 使用threadLocal...
} finally {
threadLocal.remove(); // 必须清理,防止内存泄漏
}
}
}
八、总结
本文深入讲解了Java多线程的高级内容:
- 线程池:ThreadPoolExecutor参数详解、执行流程、四种拒绝策略、监控与调优
- volatile:保证可见性和有序性,但不保证原子性,适用场景分析
- synchronized:三种使用方式、锁升级过程(偏向锁->轻量级锁->重量级锁)
- CAS:原子操作原理、ABA问题及解决方案(AtomicStampedReference)
- JUC工具类:CountDownLatch、CyclicBarrier、Semaphore的使用与对比
- ThreadLocal:线程本地变量、内存泄漏问题及预防措施
掌握这些知识,可以帮助我们在实际开发中编写出高效、安全的多线程程序。下一篇文章将继续讲解CompletableFuture异步编程、Fork/Join框架以及Java内存模型(JMM)等更深入的内容。
如果觉得本文对你有帮助,欢迎点赞、收藏、评论!
关注博主,持续更新Java技术干货!