Java多线程详解(二):线程池、同步机制与并发工具类

一、前言

本文将继续深入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 执行

执行流程总结:

  1. 任务提交:调用execute()提交任务
  2. 核心线程检查:当前线程数 < corePoolSize,创建新线程执行任务
  3. 队列检查:当前线程数 ≥ corePoolSize,任务进入workQueue等待
  4. 最大线程检查:队列已满,且当前线程数 < maximumPoolSize,创建非核心线程
  5. 拒绝策略:队列已满,且当前线程数 ≥ 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()实际上分为三步:

  1. 分配内存空间
  2. 初始化对象
  3. 将引用指向内存地址

如果没有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多线程的高级内容:

  1. 线程池:ThreadPoolExecutor参数详解、执行流程、四种拒绝策略、监控与调优
  2. volatile:保证可见性和有序性,但不保证原子性,适用场景分析
  3. synchronized:三种使用方式、锁升级过程(偏向锁->轻量级锁->重量级锁)
  4. CAS:原子操作原理、ABA问题及解决方案(AtomicStampedReference)
  5. JUC工具类:CountDownLatch、CyclicBarrier、Semaphore的使用与对比
  6. ThreadLocal:线程本地变量、内存泄漏问题及预防措施

掌握这些知识,可以帮助我们在实际开发中编写出高效、安全的多线程程序。下一篇文章将继续讲解CompletableFuture异步编程、Fork/Join框架以及Java内存模型(JMM)等更深入的内容。

如果觉得本文对你有帮助,欢迎点赞、收藏、评论!

关注博主,持续更新Java技术干货!

相关推荐
xcl09254 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐6 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长6 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn6 小时前
Spring 注解使用详解
java·spring
一条小小yu6 小时前
Spring IoC的理解
java·后端·spring
落魄实习生7 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈7 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
自强的小白8 小时前
核心功能(Service接口)
java·mybatis
乌暮9 小时前
深入理解 Java 泛型:把「万能盒子」用对、用稳
java·开发语言·后端·学习