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技术干货!

相关推荐
日月云棠2 小时前
JAVA数据结构与算法 - 基础:数组深度解析
java·后端
Java技术小馆2 小时前
微信本地数据提取/接入AI神器
java
日月云棠3 小时前
JAVA数据结构与算法 - 基础:核心概念与框架总览
java·后端
Dicky-_-zhang3 小时前
分布式系统限流熔断实战:保护微服务稳定性
java·jvm
椰猫子4 小时前
SpringBoot(简介、基础配置、整合第三方技术)
java·spring boot·spring
努力成为AK大王4 小时前
Java并发线程核心知识(一)
java·开发语言·面试
组合缺一4 小时前
Solon Flow 实战:用 50 行 YAML 实现一个请假审批流(含中断恢复、并行网关、条件分支)
java·solon·工作流·审批流·solon-flow·流程编排
iiiiyu4 小时前
面向对象和集合编程题
java·开发语言·前端·数据结构·算法·编程语言
taocarts_bidfans4 小时前
2026跨境SaaS工具选型指南:Taoify与Shopify/Shopyy/Ueeshop深度对比
java·前端·javascript·跨境电商·独立站