Java并发编程高级(线程池·Executor框架·并发集合)

1. 线程池(ThreadPoolExecutor)

1.1 线程池概述

1.1.1 为什么使用线程池

在多线程编程中,频繁地创建和销毁线程会带来很大的性能开销。线程池通过复用已创建的线程来执行任务,避免了线程创建和销毁的开销,提高了程序的执行效率。

线程池的主要优势:

  • 降低资源消耗:通过复用已创建的线程,减少线程创建和销毁的开销
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行
  • 提高线程的可管理性:可以对线程进行统一管理、监控和调优
  • 控制并发数量:可以限制同时执行的线程数量,防止系统资源耗尽
1.1.2 线程池的核心参数

线程池的核心参数决定了线程池的行为和性能,主要包括:

java 复制代码
// 线程池的7个核心参数
ThreadPoolExecutor(
    int corePoolSize,              // 核心线程数
    int maximumPoolSize,           // 最大线程数
    long keepAliveTime,            // 线程存活时间
    TimeUnit unit,                 // 时间单位
    BlockingQueue<Runnable> workQueue,  // 工作队列
    ThreadFactory threadFactory,   // 线程工厂
    RejectedExecutionHandler handler     // 拒绝策略
)

参数说明:

  • corePoolSize:核心线程数,即使线程空闲也会保留的线程数量
  • maximumPoolSize:最大线程数,线程池允许创建的最大线程数量
  • keepAliveTime:非核心线程的空闲存活时间
  • unit:时间单位,如秒、毫秒等
  • workQueue:工作队列,用于存放待执行的任务
  • threadFactory:线程工厂,用于创建新线程
  • handler:拒绝策略,当线程池无法接受新任务时的处理方式

1.2 ThreadPoolExecutor详解

1.2.1 核心参数详解

1. corePoolSize(核心线程数)

核心线程数是线程池中始终保持存活的线程数量。即使这些线程处于空闲状态,也不会被销毁。核心线程数应该根据任务的类型和系统资源来设置。

java 复制代码
// 示例:创建核心线程数为5的线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5,  // 核心线程数:始终保持5个线程
    10, // 最大线程数
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)
);

2. maximumPoolSize(最大线程数)

最大线程数是线程池允许创建的最大线程数量。当工作队列已满且当前线程数小于最大线程数时,线程池会创建新线程来执行任务。

3. keepAliveTime(线程存活时间)

当线程池中的线程数量超过核心线程数时,多余的空闲线程在等待新任务时的最大存活时间。超过这个时间,空闲线程将被回收。

4. workQueue(工作队列)

工作队列用于存放待执行的任务。常用的队列类型有:

  • ArrayBlockingQueue:基于数组的有界阻塞队列
  • LinkedBlockingQueue:基于链表的阻塞队列(可设置容量)
  • SynchronousQueue:不存储元素的阻塞队列
  • PriorityBlockingQueue:具有优先级的阻塞队列
1.2.2 线程池的状态

线程池有5种状态,通过一个原子整型变量来维护:

java 复制代码
// 线程池状态(用高3位表示)
RUNNING    = -1 << COUNT_BITS;  // 运行中:接受新任务,处理队列任务
SHUTDOWN   =  0 << COUNT_BITS;   // 关闭:不接受新任务,但处理队列任务
STOP       =  1 << COUNT_BITS;   // 停止:不接受新任务,不处理队列任务,中断正在执行的任务
TIDYING    =  2 << COUNT_BITS;   // 整理:所有任务已终止,工作线程数为0
TERMINATED =  3 << COUNT_BITS;   // 终止:terminated()方法执行完成

状态转换流程:

  1. RUNNING → SHUTDOWN :调用shutdown()方法
  2. RUNNING/SHUTDOWN → STOP :调用shutdownNow()方法
  3. STOP → TIDYING:当线程池和队列都为空时
  4. SHUTDOWN → TIDYING:当线程池为空时
  5. TIDYING → TERMINATED :当terminated()方法执行完成时

1.3 线程池的执行流程

1.3.1 execute()方法流程

当向线程池提交任务时,执行流程如下:

java 复制代码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    
    int c = ctl.get();
    // 1. 如果当前线程数 < 核心线程数,创建新线程执行任务
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    
    // 2. 如果线程池处于运行状态,将任务添加到工作队列
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        // 再次检查线程池状态,如果不在运行状态,移除任务并执行拒绝策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        // 如果当前没有工作线程,创建一个
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 3. 如果队列已满,尝试创建新线程(不超过最大线程数)
    else if (!addWorker(command, false))
        // 4. 如果创建失败,执行拒绝策略
        reject(command);
}

执行流程图:

markdown 复制代码
提交任务
  ↓
当前线程数 < 核心线程数?
  ├─ 是 → 创建新线程执行任务
  └─ 否 → 工作队列未满?
          ├─ 是 → 将任务加入队列
          └─ 否 → 当前线程数 < 最大线程数?
                  ├─ 是 → 创建新线程执行任务
                  └─ 否 → 执行拒绝策略
1.3.2 submit()方法流程

submit()方法可以提交RunnableCallable任务,并返回Future对象:

java 复制代码
// 提交Runnable任务
Future<?> submit(Runnable task);

// 提交Callable任务
<T> Future<T> submit(Callable<T> task);

// 提交Runnable任务并指定返回值
<T> Future<T> submit(Runnable task, T result);

示例代码:

java 复制代码
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(5);

// 提交Runnable任务
Future<?> future1 = executor.submit(() -> {
    System.out.println("执行任务1");
});

// 提交Callable任务
Future<String> future2 = executor.submit(() -> {
    Thread.sleep(1000);
    return "任务执行完成";
});

// 获取结果
try {
    String result = future2.get(); // 阻塞等待结果
    System.out.println(result);
} catch (Exception e) {
    e.printStackTrace();
}

1.4 工作队列(BlockingQueue)

1.4.1 ArrayBlockingQueue

基于数组的有界阻塞队列,必须指定容量:

java 复制代码
// 创建容量为10的数组阻塞队列
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);

// 使用示例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(10)
);

特点:

  • 有界队列,容量固定
  • 先进先出(FIFO)
  • 适合任务数量可预估的场景
1.4.2 LinkedBlockingQueue

基于链表的阻塞队列,可以是有界或无界的:

java 复制代码
// 无界队列(默认容量为Integer.MAX_VALUE)
BlockingQueue<Runnable> queue1 = new LinkedBlockingQueue<>();

// 有界队列,指定容量为100
BlockingQueue<Runnable> queue2 = new LinkedBlockingQueue<>(100);

特点:

  • 可以是有界或无界
  • 先进先出(FIFO)
  • 吞吐量通常高于ArrayBlockingQueue
1.4.3 SynchronousQueue

不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作:

java 复制代码
// 创建同步队列
BlockingQueue<Runnable> queue = new SynchronousQueue<>();

// 使用示例:适合任务处理速度快的场景
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
    new SynchronousQueue<>()
);

特点:

  • 不存储元素,每个插入必须等待移除
  • 适合任务处理速度快的场景
  • 可以避免任务在队列中堆积

1.5 拒绝策略(RejectedExecutionHandler)

当线程池无法接受新任务时(线程池已关闭或队列已满且线程数达到最大值),会执行拒绝策略。

1.5.1 内置拒绝策略

1. AbortPolicy(默认策略)

直接抛出异常,拒绝新任务:

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(10),
    new ThreadPoolExecutor.AbortPolicy() // 抛出RejectedExecutionException
);

2. CallerRunsPolicy

由调用线程执行任务:

java 复制代码
new ThreadPoolExecutor.CallerRunsPolicy() // 由提交任务的线程执行

3. DiscardPolicy

直接丢弃任务,不抛出异常:

java 复制代码
new ThreadPoolExecutor.DiscardPolicy() // 静默丢弃任务

4. DiscardOldestPolicy

丢弃队列中最老的任务,然后尝试提交新任务:

java 复制代码
new ThreadPoolExecutor.DiscardOldestPolicy() // 丢弃最老的任务
1.5.2 自定义拒绝策略
java 复制代码
// 自定义拒绝策略:记录日志并保存任务
public class CustomRejectedHandler implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 记录日志
        System.err.println("任务被拒绝:" + r.toString());
        // 可以保存到数据库或文件,稍后重试
        // saveTaskToDatabase(r);
    }
}

// 使用自定义拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(10),
    new CustomRejectedHandler()
);

1.6 线程池的关闭

1.6.1 shutdown()方法

优雅关闭线程池,不再接受新任务,但会等待已提交的任务执行完成:

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(5);

// 提交任务
for (int i = 0; i < 10; i++) {
    executor.submit(() -> {
        System.out.println("执行任务");
    });
}

// 关闭线程池
executor.shutdown();

// 等待所有任务完成,最多等待60秒
try {
    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
        executor.shutdownNow(); // 强制关闭
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
    Thread.currentThread().interrupt();
}
1.6.2 shutdownNow()方法

立即关闭线程池,尝试停止所有正在执行的任务,并返回等待执行的任务列表:

java 复制代码
// 立即关闭
List<Runnable> pendingTasks = executor.shutdownNow();
System.out.println("未执行的任务数:" + pendingTasks.size());

1.7 线程工厂(ThreadFactory)

线程工厂用于创建新线程,可以自定义线程的名称、优先级、守护线程属性等。

1.7.1 默认线程工厂
java 复制代码
// 默认线程工厂创建的线程名称格式:pool-{poolNumber}-thread-{threadNumber}
ThreadFactory defaultFactory = Executors.defaultThreadFactory();
Thread thread = defaultFactory.newThread(() -> System.out.println("任务"));
// 线程名称:pool-1-thread-1
1.7.2 自定义线程工厂

自定义线程工厂可以设置线程的名称、优先级、异常处理器等:

java 复制代码
public class CustomThreadFactory implements ThreadFactory {
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    private final String namePrefix;
    
    public CustomThreadFactory(String namePrefix) {
        this.namePrefix = namePrefix;
    }
    
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r, namePrefix + "-" + threadNumber.getAndIncrement());
        
        // 设置为非守护线程
        thread.setDaemon(false);
        
        // 设置优先级
        thread.setPriority(Thread.NORM_PRIORITY);
        
        // 设置异常处理器
        thread.setUncaughtExceptionHandler((t, e) -> {
            System.err.println("线程 " + t.getName() + " 发生异常:" + e.getMessage());
            e.printStackTrace();
        });
        
        return thread;
    }
}

// 使用自定义线程工厂
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),
    new CustomThreadFactory("MyPool"),  // 自定义线程工厂
    new ThreadPoolExecutor.AbortPolicy()
);
1.7.3 线程工厂的最佳实践

1. 使用有意义的线程名称

java 复制代码
// 好的做法:线程名称包含业务信息
new CustomThreadFactory("OrderProcessPool")

// 不好的做法:使用默认名称
Executors.defaultThreadFactory()

2. 设置异常处理器

java 复制代码
thread.setUncaughtExceptionHandler((t, e) -> {
    // 记录日志
    logger.error("线程执行异常", e);
    // 发送告警
    alertService.sendAlert("线程池异常", e);
});

3. 设置合理的优先级

java 复制代码
// 一般使用默认优先级即可
thread.setPriority(Thread.NORM_PRIORITY);

1.8 线程池的内部实现原理

重要说明: 以下内容都是ThreadPoolExecutor类的内部实现细节 ,这些类和方法都是private的,我们平时使用线程池时不需要直接操作它们。了解这些原理有助于更好地理解线程池的工作机制。

1.8.1 ctl变量

线程池使用一个原子整型变量ctl来同时表示线程池的状态和线程数量:

java 复制代码
// ctl = (runState << COUNT_BITS) | workerCount
// 高3位表示状态,低29位表示线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 状态掩码(高3位)
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// 获取状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 获取线程数
private static int workerCountOf(int c)  { return c & CAPACITY; }

设计优势:

  • 使用一个变量同时表示状态和数量,保证原子性
  • 减少内存占用
  • 状态和数量的更新是原子操作

通俗理解:

  • ctl就像一个"状态计数器",用一个数字同时记录"线程池是什么状态"和"有多少个线程"
  • 高3位存状态(RUNNING、SHUTDOWN等),低29位存线程数量
1.8.2 Worker类

WorkerThreadPoolExecutor内部类 ,用于封装工作线程。每个工作线程都对应一个Worker对象。

java 复制代码
// 这是ThreadPoolExecutor的内部类(private final class)
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    final Thread thread;        // 工作线程(真正的线程对象)
    Runnable firstTask;         // 第一个任务(创建Worker时传入的任务)
    volatile long completedTasks; // 完成的任务数
    
    Worker(Runnable firstTask) {
        setState(-1); // 禁止中断,直到runWorker
        this.firstTask = firstTask;
        // 创建真正的线程,这个线程执行的就是Worker的run()方法
        this.thread = getThreadFactory().newThread(this);
    }
    
    // Worker实现了Runnable接口,所以Worker本身就是一个任务
    public void run() {
        // 调用ThreadPoolExecutor的runWorker方法
        runWorker(this);
    }
    
    // 使用AQS实现锁,用于控制线程的中断
    protected boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }
}

Worker的作用(通俗理解):

  • Worker就像一个"工人"的档案,记录了这个工人(线程)的信息
  • 每个Worker包含一个真正的Thread对象(真正的线程)
  • Worker还记录了分配给它的第一个任务,以及完成了多少个任务
  • 当线程池创建新线程时,会创建一个Worker对象,然后启动Worker中的线程

工作流程:

scss 复制代码
1. 线程池需要创建新线程时
   ↓
2. 创建一个Worker对象(包含Thread和firstTask)
   ↓
3. 启动Worker中的Thread
   ↓
4. Thread执行Worker的run()方法
   ↓
5. run()方法调用ThreadPoolExecutor的runWorker()方法
   ↓
6. runWorker()方法开始执行任务

示例说明:

java 复制代码
// 当我们创建线程池时
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, ...);

// 当我们提交任务时
executor.execute(() -> System.out.println("任务"));

// 线程池内部会:
// 1. 创建一个Worker对象
Worker worker = new Worker(task);  // task是我们要执行的任务

// 2. Worker内部创建Thread
Thread thread = new Thread(worker);  // worker实现了Runnable

// 3. 启动线程
thread.start();  // 这时会执行worker.run()

// 4. worker.run()调用runWorker(worker)
runWorker(worker);  // 开始执行任务
1.8.3 runWorker()方法

runWorker()ThreadPoolExecutor私有方法 ,是工作线程执行任务的核心逻辑。这个方法会被Worker.run()调用。

java 复制代码
// 这是ThreadPoolExecutor的私有方法
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();  // 当前工作线程
    Runnable task = w.firstTask;         // 获取Worker的第一个任务
    w.firstTask = null;                  // 清空,因为要执行了
    w.unlock();                          // 允许中断
    boolean completedAbruptly = true;
    
    try {
        // 核心循环:不断获取任务并执行
        // 条件:task不为null(第一个任务)或者能从队列中获取到任务
        while (task != null || (task = getTask()) != null) {
            w.lock();  // 加锁,防止被中断
            
            // 检查线程池状态,如果已停止,中断当前线程
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            
            try {
                // 执行前钩子(可以重写,用于监控、日志等)
                beforeExecute(wt, task);
                
                Throwable thrown = null;
                try {
                    // 真正执行任务!这里调用的是我们提交的Runnable的run()方法
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    // 执行后钩子(可以重写,用于监控、日志等)
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;              // 清空任务引用
                w.completedTasks++;       // 完成任务数+1
                w.unlock();               // 解锁
            }
        }
        completedAbruptly = false;  // 正常退出
    } finally {
        // 处理工作线程退出(从workers集合中移除,可能创建新线程等)
        processWorkerExit(w, completedAbruptly);
    }
}

执行流程(通俗理解):

scss 复制代码
1. Worker的线程启动后,调用runWorker(worker)
   ↓
2. 先执行Worker的第一个任务(如果有)
   ↓
3. 进入循环:
   - 调用getTask()从队列中获取任务
   - 如果获取到任务,就执行task.run()
   - 执行完后继续循环,获取下一个任务
   ↓
4. 如果getTask()返回null(队列空了或线程池关闭),退出循环
   ↓
5. 调用processWorkerExit()处理线程退出

关键点:

  • runWorker()是工作线程的"主循环",不断从队列中取任务并执行
  • task.run()这里才是真正执行我们提交的任务
  • 一个线程可以执行多个任务(这就是线程复用的原理)

示例说明:

java 复制代码
// 我们提交3个任务
executor.execute(() -> System.out.println("任务1"));
executor.execute(() -> System.out.println("任务2"));
executor.execute(() -> System.out.println("任务3"));

// 线程池内部执行流程:
// 1. 创建Worker,启动线程
// 2. 线程执行runWorker(worker)
// 3. runWorker中:
//    - 第一次循环:getTask()返回"任务1",执行task.run() → 打印"任务1"
//    - 第二次循环:getTask()返回"任务2",执行task.run() → 打印"任务2"
//    - 第三次循环:getTask()返回"任务3",执行task.run() → 打印"任务3"
//    - 第四次循环:getTask()返回null(队列空了),退出循环
// 4. 一个线程执行了3个任务,这就是线程复用!
1.8.4 getTask()方法

getTask()ThreadPoolExecutor私有方法 ,用于从工作队列中获取任务。这个方法会被runWorker()调用。

java 复制代码
// 这是ThreadPoolExecutor的私有方法
private Runnable getTask() {
    boolean timedOut = false;  // 是否超时
    
    for (;;) {  // 无限循环,直到获取到任务或返回null
        int c = ctl.get();
        int rs = runStateOf(c);  // 获取线程池状态
        
        // 检查线程池状态:如果已关闭且(已停止或队列为空),返回null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;  // 返回null,runWorker会退出循环
        }
        
        int wc = workerCountOf(c);  // 获取当前线程数
        
        // 判断是否允许超时:
        // - 如果允许核心线程超时,或者当前线程数超过核心线程数,则允许超时
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
        
        // 检查是否需要回收线程(线程数过多或超时)
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;  // 返回null,线程会被回收
            continue;
        }
        
        try {
            // 从队列中获取任务
            Runnable r = timed ?
                // 允许超时:使用poll(),等待keepAliveTime时间
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                // 不允许超时:使用take(),一直阻塞等待
                workQueue.take();
            
            if (r != null)
                return r;  // 获取到任务,返回
            timedOut = true;  // 超时了,标记一下
        } catch (InterruptedException retry) {
            timedOut = false;  // 被中断,重试
        }
    }
}

关键点(通俗理解):

  1. 核心线程 vs 非核心线程的获取方式:

    • 核心线程 :使用workQueue.take(),会一直阻塞等待,直到有任务
    • 非核心线程 :使用workQueue.poll(keepAliveTime),等待keepAliveTime时间,如果还没任务就返回null
  2. 线程回收机制:

    • 非核心线程如果超时(keepAliveTime时间内没获取到任务),返回null
    • 返回null后,runWorker()退出循环,线程被回收
    • 核心线程默认不会超时,会一直等待任务
  3. 线程池关闭时的处理:

    • 如果线程池已关闭,getTask()返回null
    • 所有工作线程的runWorker()都会退出,线程结束

示例说明:

java 复制代码
// 创建线程池:核心线程数=2,最大线程数=5,keepAliveTime=60秒
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 5, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(10)
);

// 场景1:提交2个任务(核心线程执行)
executor.execute(task1);
executor.execute(task2);
// 两个核心线程分别执行任务,执行完后:
// - 核心线程调用getTask(),使用take()方法,一直阻塞等待新任务

// 场景2:突然来了10个任务
// - 2个核心线程执行2个任务
// - 队列中放入8个任务
// - 创建3个非核心线程执行剩余任务
// 执行完后:
// - 核心线程:getTask()使用take(),一直等待
// - 非核心线程:getTask()使用poll(60秒),60秒内没任务就返回null,线程被回收

// 场景3:调用shutdown()
// - 所有线程的getTask()检查到线程池已关闭,返回null
// - 所有线程的runWorker()退出循环,线程结束

总结:

  • getTask()是工作线程从队列中"取任务"的方法
  • 核心线程会一直等待(take()),非核心线程会超时(poll()
  • 返回null表示线程应该退出,线程会被回收
1.8.5 Worker、runWorker()、getTask()的关系总结

它们都是ThreadPoolExecutor的内部实现,关系如下:

scss 复制代码
ThreadPoolExecutor(线程池)
  │
  ├─ Worker(内部类,封装工作线程)
  │   │
  │   ├─ Thread thread(真正的线程对象)
  │   ├─ Runnable firstTask(第一个任务)
  │   └─ run()方法 → 调用runWorker(this)
  │
  ├─ runWorker(Worker w)(私有方法,工作线程的主循环)
  │   │
  │   ├─ 执行Worker的第一个任务
  │   ├─ 循环调用getTask()获取任务
  │   ├─ 执行任务:task.run()
  │   └─ 处理线程退出
  │
  └─ getTask()(私有方法,从队列获取任务)
      │
      ├─ 核心线程:使用take(),一直阻塞等待
      └─ 非核心线程:使用poll(),超时返回null

完整的工作流程:

java 复制代码
// 1. 我们提交任务
executor.execute(() -> System.out.println("任务"));

// 2. 线程池内部创建Worker
Worker worker = new Worker(task);
// Worker内部创建Thread
Thread thread = new Thread(worker);

// 3. 启动线程
thread.start();
// → 执行worker.run()
// → 调用runWorker(worker)

// 4. runWorker()开始工作
runWorker(worker) {
    // 执行第一个任务
    task.run();  // 打印"任务"
    
    // 循环获取新任务
    while ((task = getTask()) != null) {
        task.run();  // 执行任务
    }
    
    // getTask()返回null,退出循环
    // 处理线程退出
}

// 5. getTask()从队列中获取任务
getTask() {
    // 核心线程:一直等待
    Runnable task = workQueue.take();
    return task;
    
    // 非核心线程:超时等待
    Runnable task = workQueue.poll(60, TimeUnit.SECONDS);
    return task;  // 或返回null(超时)
}

关键理解:

  1. Worker:是线程的"包装器",每个工作线程对应一个Worker
  2. runWorker():是线程的"工作循环",不断取任务、执行任务
  3. getTask():是"取任务"的方法,从队列中获取任务
  4. 这三个都是内部实现,我们平时使用线程池时不需要直接操作它们

为什么需要了解这些?

  • 理解线程池的工作原理
  • 调试线程池相关问题时有用
  • 理解线程复用机制(一个线程执行多个任务)
  • 理解线程回收机制(非核心线程超时回收)

1.9 工作队列详解

1.9.1 队列选择策略

不同的队列适用于不同的场景:

1. ArrayBlockingQueue(有界队列)

java 复制代码
// 适合:任务数量可预估,需要控制内存使用
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100)  // 有界队列,最多100个任务
);

特点:

  • 容量固定,不会无限增长
  • 适合任务数量可预估的场景
  • 队列满时会触发拒绝策略或创建新线程

2. LinkedBlockingQueue(无界或有界队列)

java 复制代码
// 无界队列:适合任务数量不可预估,但处理速度快的场景
ThreadPoolExecutor executor1 = new ThreadPoolExecutor(
    5, 5, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>()  // 无界队列
);

// 有界队列:适合需要控制内存的场景
ThreadPoolExecutor executor2 = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(200)  // 有界队列
);

特点:

  • 可以是有界或无界
  • 吞吐量通常高于ArrayBlockingQueue
  • 无界队列可能导致内存溢出

3. SynchronousQueue(同步队列)

java 复制代码
// 适合:任务处理速度快,不需要缓存任务
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
    new SynchronousQueue<>()
);

特点:

  • 不存储元素,每个插入必须等待移除
  • 适合任务处理速度快的场景
  • 可以避免任务在队列中堆积

4. PriorityBlockingQueue(优先级队列)

java 复制代码
// 适合:需要按优先级执行任务的场景
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new PriorityBlockingQueue<>(100, 
        (r1, r2) -> {
            // 自定义优先级比较逻辑
            Task t1 = (Task) r1;
            Task t2 = (Task) r2;
            return t2.getPriority() - t1.getPriority(); // 优先级高的先执行
        })
);

特点:

  • 按优先级排序
  • 适合需要优先处理某些任务的场景

5. DelayQueue(延迟队列)

java 复制代码
// 适合:需要延迟执行或定时执行的场景
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new DelayQueue<>()
);
1.9.2 队列容量设置

队列容量的设置需要考虑以下因素:

java 复制代码
// 计算公式:队列容量 = (最大线程数 - 核心线程数) * 每个线程处理任务的时间 / 任务到达间隔

// 示例:假设
// - 核心线程数:5
// - 最大线程数:10
// - 每个任务处理时间:100ms
// - 任务到达间隔:50ms
// 队列容量 = (10 - 5) * 100 / 50 = 10

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(10)  // 根据计算设置容量
);

建议:

  • 不要使用无界队列(除非任务处理速度非常快)
  • 队列容量应该根据实际业务场景设置
  • 可以通过监控队列大小来调整容量

1.10 线程池的生命周期管理

1.10.1 线程池的创建
java 复制代码
// 方式1:直接创建ThreadPoolExecutor(推荐)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),
    new CustomThreadFactory("MyPool"),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

// 方式2:使用Executors(不推荐,仅用于测试)
ExecutorService executor = Executors.newFixedThreadPool(5);
1.10.2 线程池的运行
java 复制代码
// 提交任务
executor.execute(() -> {
    // 执行任务
});

// 提交有返回值的任务
Future<String> future = executor.submit(() -> {
    return "结果";
});

// 批量提交任务
List<Callable<String>> tasks = Arrays.asList(
    () -> "任务1",
    () -> "任务2",
    () -> "任务3"
);
List<Future<String>> futures = executor.invokeAll(tasks);
1.10.3 线程池的关闭

优雅关闭的步骤:

java 复制代码
public void shutdownThreadPool(ThreadPoolExecutor executor) {
    // 1. 停止接受新任务
    executor.shutdown();
    
    try {
        // 2. 等待已提交的任务完成
        if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
            // 3. 如果超时,强制关闭
            executor.shutdownNow();
            
            // 4. 再次等待,确保所有任务都停止
            if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
                System.err.println("线程池未能正常关闭");
            }
        }
    } catch (InterruptedException e) {
        // 5. 如果当前线程被中断,强制关闭
        executor.shutdownNow();
        Thread.currentThread().interrupt();
    }
}
1.10.4 线程池的监控
java 复制代码
public class ThreadPoolMonitor {
    private final ThreadPoolExecutor executor;
    
    public ThreadPoolMonitor(ThreadPoolExecutor executor) {
        this.executor = executor;
    }
    
    public void printStatus() {
        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.getCompletedTaskCount());
        System.out.println("总任务数: " + executor.getTaskCount());
        System.out.println("队列大小: " + executor.getQueue().size());
        System.out.println("队列剩余容量: " + 
            (executor.getQueue().remainingCapacity()));
    }
    
    // 定期监控
    public void startMonitoring(long period, TimeUnit unit) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            printStatus();
        }, 0, period, unit);
    }
}

1.11 线程池的性能优化

1.11.1 参数调优

1. 核心线程数的设置

java 复制代码
// CPU密集型:核心线程数 = CPU核心数 + 1
int cpuCount = Runtime.getRuntime().availableProcessors();
int corePoolSize = cpuCount + 1;

// IO密集型:核心线程数 = CPU核心数 * 2
int corePoolSize = cpuCount * 2;

// 混合型:根据实际测试调整
int corePoolSize = cpuCount;

2. 最大线程数的设置

java 复制代码
// 一般设置为核心线程数的2倍
int maximumPoolSize = corePoolSize * 2;

// 或者根据业务需求设置
int maximumPoolSize = corePoolSize + 预期峰值任务数 / 单个任务处理时间;

3. 队列容量的设置

java 复制代码
// 根据业务场景设置
// 队列容量 = (最大线程数 - 核心线程数) * 每个线程处理任务的时间 / 任务到达间隔

// 或者根据内存限制设置
int queueCapacity = 可用内存 / 单个任务占用内存;
1.11.2 允许核心线程超时
java 复制代码
// 允许核心线程超时,提高资源利用率
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)
);

// 允许核心线程超时
executor.allowCoreThreadTimeOut(true);

适用场景:

  • 任务到达不规律
  • 需要节省资源
  • 可以接受线程创建的开销
1.11.3 预启动核心线程
java 复制代码
// 预启动所有核心线程,提高响应速度
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100)
);

// 预启动所有核心线程
executor.prestartAllCoreThreads();

适用场景:

  • 需要快速响应
  • 系统启动时可以接受线程创建的开销

1.12 线程池的异常处理

1.12.1 execute()方法的异常处理

execute()方法提交的任务如果抛出异常,异常会被吞掉:

java 复制代码
executor.execute(() -> {
    throw new RuntimeException("异常");  // 异常会被吞掉
});

// 解决方案1:在任务内部捕获异常
executor.execute(() -> {
    try {
        // 执行任务
    } catch (Exception e) {
        // 处理异常
        logger.error("任务执行异常", e);
    }
});

// 解决方案2:使用自定义的ThreadFactory设置异常处理器
ThreadFactory factory = r -> {
    Thread t = new Thread(r);
    t.setUncaughtExceptionHandler((thread, ex) -> {
        logger.error("线程执行异常", ex);
    });
    return t;
};
1.12.2 submit()方法的异常处理

submit()方法提交的任务如果抛出异常,异常会被包装在Future中:

java 复制代码
Future<?> future = executor.submit(() -> {
    throw new RuntimeException("异常");
});

try {
    future.get();  // 这里会抛出ExecutionException
} catch (ExecutionException e) {
    Throwable cause = e.getCause();  // 获取原始异常
    logger.error("任务执行异常", cause);
}
1.12.3 重写afterExecute()方法

可以重写ThreadPoolExecutorafterExecute()方法来统一处理异常:

java 复制代码
public class CustomThreadPoolExecutor extends ThreadPoolExecutor {
    public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
                                   long keepAliveTime, TimeUnit unit,
                                   BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }
    
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        
        // 处理异常
        if (t != null) {
            logger.error("任务执行异常", t);
            // 可以发送告警、记录日志等
        }
    }
}

1.13 线程池的常见问题和解决方案

1.13.1 线程池满了怎么办?

问题: 线程池满了,新任务被拒绝。

解决方案:

java 复制代码
// 1. 增加线程池大小
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    10, 20, 60L, TimeUnit.SECONDS,  // 增加核心线程数和最大线程数
    new LinkedBlockingQueue<>(200)  // 增加队列容量
);

// 2. 使用合适的拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

// 3. 监控线程池状态,及时调整
ThreadPoolMonitor monitor = new ThreadPoolMonitor(executor);
monitor.startMonitoring(1, TimeUnit.MINUTES);
1.13.2 任务执行时间过长

问题: 任务执行时间过长,占用线程资源。

解决方案:

java 复制代码
// 1. 使用Future.get()设置超时
Future<String> future = executor.submit(() -> {
    // 长时间运行的任务
    return processLongTask();
});

try {
    String result = future.get(30, TimeUnit.SECONDS);  // 30秒超时
} catch (TimeoutException e) {
    future.cancel(true);  // 取消任务
    logger.warn("任务执行超时,已取消");
}

// 2. 将长时间任务拆分为多个短任务
executor.submit(() -> {
    List<Task> tasks = splitLongTask();
    for (Task task : tasks) {
        executor.submit(() -> processTask(task));
    }
});
1.13.3 线程池内存泄漏

问题: 线程池中的线程持有对象引用,导致内存泄漏。

解决方案:

java 复制代码
// 1. 使用ThreadLocal后及时清理
executor.execute(() -> {
    try {
        ThreadLocal<String> local = new ThreadLocal<>();
        local.set("value");
        // 使用ThreadLocal
    } finally {
        local.remove();  // 及时清理
    }
});

// 2. 避免在线程中持有大对象的引用
// 3. 定期检查线程池状态,及时关闭不用的线程池
1.13.4 线程池无法关闭

问题: 调用shutdown()后,线程池无法正常关闭。

解决方案:

java 复制代码
// 1. 确保所有任务都能正常完成
executor.execute(() -> {
    try {
        // 执行任务
    } catch (Exception e) {
        // 处理异常,避免任务卡住
    }
});

// 2. 使用shutdownNow()强制关闭
executor.shutdown();
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
    executor.shutdownNow();
}

// 3. 检查是否有线程在等待IO操作
// 确保IO操作有超时设置

1.14 线程池的最佳实践

1.14.1 创建线程池的最佳实践
java 复制代码
// 1. 使用有意义的线程名称
ThreadFactory factory = new CustomThreadFactory("OrderProcessPool");

// 2. 设置合理的参数
int cpuCount = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    cpuCount,                    // 核心线程数
    cpuCount * 2,                // 最大线程数
    60L, TimeUnit.SECONDS,       // 线程存活时间
    new LinkedBlockingQueue<>(1000),  // 有界队列
    factory,                     // 自定义线程工厂
    new ThreadPoolExecutor.CallerRunsPolicy()  // 拒绝策略
);

// 3. 预启动核心线程(可选)
executor.prestartAllCoreThreads();
1.14.2 使用线程池的最佳实践
java 复制代码
// 1. 使用try-with-resources或确保关闭线程池
try (ExecutorService executor = Executors.newFixedThreadPool(5)) {
    // 使用线程池
}  // 自动关闭

// 2. 提交任务时处理异常
executor.submit(() -> {
    try {
        // 执行任务
    } catch (Exception e) {
        logger.error("任务执行异常", e);
    }
});

// 3. 使用Future.get()时设置超时
Future<String> future = executor.submit(() -> "结果");
try {
    String result = future.get(30, TimeUnit.SECONDS);
} catch (TimeoutException e) {
    future.cancel(true);
}
1.14.3 监控线程池的最佳实践
java 复制代码
// 1. 定期监控线程池状态
ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1);
monitor.scheduleAtFixedRate(() -> {
    ThreadPoolMonitor.printStatus(executor);
}, 0, 1, TimeUnit.MINUTES);

// 2. 设置告警阈值
if (executor.getQueue().size() > 1000) {
    alertService.sendAlert("线程池队列积压严重");
}

// 3. 记录线程池指标
metricsCollector.record("threadpool.queue.size", executor.getQueue().size());
metricsCollector.record("threadpool.active.count", executor.getActiveCount());

1.15 线程池的监控

可以通过以下方法监控线程池的状态:

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(...);

// 获取线程池状态信息
int corePoolSize = executor.getCorePoolSize();        // 核心线程数
int maximumPoolSize = executor.getMaximumPoolSize();   // 最大线程数
int poolSize = executor.getPoolSize();                // 当前线程数
int activeCount = executor.getActiveCount();           // 正在执行任务的线程数
long completedTaskCount = executor.getCompletedTaskCount(); // 已完成的任务数
long taskCount = executor.getTaskCount();             // 总任务数
int queueSize = executor.getQueue().size();           // 队列中的任务数
int queueRemainingCapacity = executor.getQueue().remainingCapacity(); // 队列剩余容量

监控指标说明:

  • poolSize:当前线程池中的线程数量
  • activeCount:正在执行任务的线程数量
  • completedTaskCount:已完成的任务数量
  • taskCount:总任务数量(已完成 + 正在执行 + 队列中)
  • queueSize:队列中的任务数量
  • queueRemainingCapacity:队列剩余容量

2. Executor框架

2.1 Executor框架概述

Executor框架是Java 5引入的,用于简化线程管理和任务执行的框架。它提供了线程池的实现,将任务的提交和执行解耦。

2.1.1 Executor接口

Executor接口是最顶层的接口,只定义了一个方法:

java 复制代码
public interface Executor {
    void execute(Runnable command);
}
2.1.2 ExecutorService接口

ExecutorService接口扩展了Executor接口,提供了更丰富的功能:

java 复制代码
public interface ExecutorService extends Executor {
    // 关闭线程池
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit);
    
    // 提交任务
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    
    // 批量提交任务
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks);
    <T> T invokeAny(Collection<? extends Callable<T>> tasks);
}
2.1.3 ScheduledExecutorService接口

ScheduledExecutorService接口支持定时和周期性任务:

java 复制代码
public interface ScheduledExecutorService extends ExecutorService {
    // 延迟执行
    ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
    
    // 周期性执行(固定延迟)
    ScheduledFuture<?> scheduleAtFixedRate(Runnable command, 
                                         long initialDelay, 
                                         long period, 
                                         TimeUnit unit);
    
    // 周期性执行(固定间隔)
    ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, 
                                              long initialDelay, 
                                              long delay, 
                                              TimeUnit unit);
}

2.2 预定义线程池

Executors类提供了几种预定义的线程池,但不推荐在生产环境使用,因为它们的参数设置可能不适合实际场景。

2.2.1 newFixedThreadPool

创建固定大小的线程池:

java 复制代码
// 创建固定大小为5的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);

// 内部实现(不推荐直接使用)
new ThreadPoolExecutor(
    nThreads,                    // 核心线程数 = 最大线程数
    nThreads,                    // 最大线程数
    0L, TimeUnit.MILLISECONDS,   // 线程不回收
    new LinkedBlockingQueue<>()  // 无界队列(可能导致OOM)
);

问题: 使用无界队列,可能导致内存溢出。

2.2.2 newCachedThreadPool

创建可缓存的线程池:

java 复制代码
// 创建可缓存的线程池
ExecutorService executor = Executors.newCachedThreadPool();

// 内部实现(不推荐直接使用)
new ThreadPoolExecutor(
    0,                           // 核心线程数为0
    Integer.MAX_VALUE,           // 最大线程数无限制(可能导致创建过多线程)
    60L, TimeUnit.SECONDS,
    new SynchronousQueue<>()     // 同步队列
);

问题: 最大线程数无限制,可能导致创建过多线程,耗尽系统资源。

2.2.3 newSingleThreadExecutor

创建单线程的线程池:

java 复制代码
// 创建单线程的线程池
ExecutorService executor = Executors.newSingleThreadExecutor();

// 内部实现
new ThreadPoolExecutor(
    1, 1, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>()  // 无界队列
);

问题: 使用无界队列,可能导致内存溢出。

2.2.4 newScheduledThreadPool

创建支持定时任务的线程池:

java 复制代码
// 创建支持定时任务的线程池
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);

// 延迟执行
executor.schedule(() -> {
    System.out.println("延迟执行的任务");
}, 5, TimeUnit.SECONDS);

// 周期性执行(固定频率)
executor.scheduleAtFixedRate(() -> {
    System.out.println("周期性任务");
}, 0, 1, TimeUnit.SECONDS);

2.3 线程池的合理配置

2.3.1 CPU密集型任务

CPU密集型任务主要消耗CPU资源,线程数应该设置为CPU核心数+1:

java 复制代码
int cpuCount = Runtime.getRuntime().availableProcessors();
int threadCount = cpuCount + 1;

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    threadCount,      // 核心线程数
    threadCount,      // 最大线程数
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);
2.3.2 IO密集型任务

IO密集型任务主要等待IO操作(如网络请求、文件读写),线程数可以设置得更大:

java 复制代码
// 线程数 = CPU核心数 * (1 + IO等待时间 / CPU计算时间)
// 一般可以设置为 CPU核心数 * 2
int cpuCount = Runtime.getRuntime().availableProcessors();
int threadCount = cpuCount * 2;

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    threadCount,
    threadCount * 2,
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);
2.3.3 混合型任务

对于混合型任务,可以根据任务的特点来调整线程数:

java 复制代码
// 根据实际场景调整
int cpuCount = Runtime.getRuntime().availableProcessors();
int coreThreads = cpuCount;
int maxThreads = cpuCount * 2;

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    coreThreads,
    maxThreads,
    60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);

2.4 Future与Callable

2.4.1 Callable接口

Callable接口类似于Runnable,但可以返回结果和抛出异常:

java 复制代码
public interface Callable<V> {
    V call() throws Exception;
}

示例:

java 复制代码
// 创建Callable任务
Callable<String> task = () -> {
    Thread.sleep(1000);
    return "任务执行完成";
};

// 提交任务
ExecutorService executor = Executors.newFixedThreadPool(5);
Future<String> future = executor.submit(task);

// 获取结果
try {
    String result = future.get(); // 阻塞等待结果
    System.out.println(result);
} catch (Exception e) {
    e.printStackTrace();
}
2.4.2 Future接口

Future接口表示异步计算的结果:

java 复制代码
public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);  // 取消任务
    boolean isCancelled();                          // 是否已取消
    boolean isDone();                               // 是否已完成
    V get() throws InterruptedException, ExecutionException;  // 获取结果(阻塞)
    V get(long timeout, TimeUnit unit)              // 获取结果(带超时)
        throws InterruptedException, ExecutionException, TimeoutException;
}

使用示例:

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(5);

// 提交任务
Future<String> future = executor.submit(() -> {
    Thread.sleep(2000);
    return "结果";
});

// 检查是否完成
if (future.isDone()) {
    System.out.println("任务已完成");
}

// 获取结果(带超时)
try {
    String result = future.get(3, TimeUnit.SECONDS);
    System.out.println("结果:" + result);
} catch (TimeoutException e) {
    System.out.println("任务超时");
    future.cancel(true); // 取消任务
}

2.5 CompletableFuture

CompletableFuture是Java 8引入的,提供了更强大的异步编程能力。

2.5.1 创建CompletableFuture
java 复制代码
// 方式1:使用supplyAsync(有返回值)
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    return "结果";
});

// 方式2:使用runAsync(无返回值)
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
    System.out.println("执行任务");
});

// 方式3:使用自定义线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
    return "结果";
}, executor);
2.5.2 结果处理
java 复制代码
// thenApply:转换结果
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> "Hello")
    .thenApply(s -> s + " World")
    .thenApply(String::toUpperCase);

// thenAccept:消费结果(无返回值)
CompletableFuture.supplyAsync(() -> "Hello")
    .thenAccept(System.out::println);

// thenRun:执行后续操作(不依赖结果)
CompletableFuture.supplyAsync(() -> "Hello")
    .thenRun(() -> System.out.println("任务完成"));
2.5.3 组合操作
java 复制代码
// thenCompose:组合两个CompletableFuture(串行)
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = future1.thenCompose(s -> 
    CompletableFuture.supplyAsync(() -> s + " World")
);

// thenCombine:组合两个CompletableFuture(并行)
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = future1.thenCombine(future2, (s1, s2) -> s1 + " " + s2);
2.5.4 异常处理
java 复制代码
CompletableFuture<String> future = CompletableFuture
    .supplyAsync(() -> {
        if (true) {
            throw new RuntimeException("异常");
        }
        return "结果";
    })
    .exceptionally(ex -> {
        System.err.println("发生异常:" + ex.getMessage());
        return "默认值";
    });

// 或者使用handle方法
CompletableFuture<String> future2 = CompletableFuture
    .supplyAsync(() -> "结果")
    .handle((result, ex) -> {
        if (ex != null) {
            return "异常处理";
        }
        return result;
    });

3. 并发集合类

3.1 并发集合概述

并发集合是Java提供的线程安全的集合类,它们通过不同的机制来保证线程安全,性能通常优于使用synchronized包装的同步集合。

3.1.1 并发集合的分类
  • MapConcurrentHashMap
  • ListCopyOnWriteArrayList
  • SetCopyOnWriteArraySetConcurrentSkipListSet
  • QueueConcurrentLinkedQueue、各种BlockingQueue
  • 其他ConcurrentSkipListMap
3.1.2 并发集合 vs 同步集合

同步集合(不推荐):

java 复制代码
// 使用Collections.synchronizedMap包装
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
// 问题:性能差,锁粒度大

并发集合(推荐):

java 复制代码
// 使用ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
// 优点:性能好,锁粒度小

3.2 ConcurrentHashMap

ConcurrentHashMap是线程安全的HashMap实现,在JDK 1.8中进行了重大优化。

3.2.1 JDK 1.8实现原理

JDK 1.8的ConcurrentHashMap使用CAS + synchronized来实现线程安全:

java 复制代码
// 基本结构
public class ConcurrentHashMap<K,V> {
    // 数组(桶数组)
    transient volatile Node<K,V>[] table;
    
    // 节点结构
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
    }
}

核心机制:

  1. CAS操作:用于无锁的插入和更新
  2. synchronized锁:用于链表头节点,锁粒度小
  3. 分段锁思想:每个桶(数组元素)可以独立加锁
3.2.2 put()方法实现
java 复制代码
public V put(K key, V value) {
    return putVal(key, value, false);
}

final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 1. 计算hash值
    int hash = spread(key.hashCode());
    
    // 2. 遍历链表
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        
        // 3. 如果表为空,初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        
        // 4. 如果桶为空,使用CAS插入
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
                break;
        }
        
        // 5. 如果正在扩容,帮助扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        
        // 6. 否则,使用synchronized锁住链表头节点
        else {
            synchronized (f) {
                // 在链表中查找或插入
            }
        }
    }
}

关键点:

  • 使用CAS进行无锁插入(桶为空时)
  • 使用synchronized锁住链表头节点(桶不为空时)
  • 锁粒度小,只锁一个桶,不影响其他桶的操作
3.2.3 get()方法实现
java 复制代码
public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode());
    
    // 1. 如果表不为空且桶不为空
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {
        
        // 2. 如果第一个节点就是要找的节点
        if ((eh = e.hash) == h) {
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        
        // 3. 如果hash值为负数,可能是红黑树或正在扩容
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        
        // 4. 遍历链表
        while ((e = e.next) != null) {
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

特点:

  • 无锁读取:get操作不需要加锁,通过volatile保证可见性
  • 性能高:读操作不会阻塞写操作
3.2.4 size()方法实现

ConcurrentHashMapsize()方法不是简单地返回一个变量,而是通过累加各个段的大小来计算:

java 复制代码
public int size() {
    long n = sumCount();
    return ((n < 0L) ? 0 :
            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
            (int)n);
}

// 累加所有CounterCell的值
final long sumCount() {
    CounterCell[] as = counterCells; CounterCell a;
    long sum = baseCount;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

注意: size()方法返回的是近似值,因为并发环境下,大小可能随时变化。

3.3 CopyOnWriteArrayList

CopyOnWriteArrayList是线程安全的ArrayList实现,采用写时复制机制。

3.3.1 写时复制机制

原理:

  • 读操作:直接读取,不需要加锁
  • 写操作:先复制整个数组,在副本上修改,然后替换原数组
java 复制代码
public class CopyOnWriteArrayList<E> {
    // 底层数组,使用volatile保证可见性
    private transient volatile Object[] array;
    
    // 写操作:复制数组
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 复制数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            // 替换原数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    // 读操作:直接读取,不需要加锁
    public E get(int index) {
        return get(getArray(), index);
    }
}
3.3.2 适用场景

适合:

  • 读多写少的场景
  • 对数据一致性要求不高的场景

不适合:

  • 写操作频繁的场景(每次写都要复制数组,开销大)
  • 对实时性要求高的场景(写操作可能读取到旧数据)

示例:

java 复制代码
// 适合场景:监听器列表(读多写少)
CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();

// 添加监听器(写操作少)
listeners.add(new Listener());

// 遍历监听器(读操作多)
for (Listener listener : listeners) {
    listener.onEvent();
}

3.4 BlockingQueue详解

BlockingQueue是阻塞队列接口,提供了线程安全的队列操作。

3.4.1 阻塞方法
java 复制代码
public interface BlockingQueue<E> extends Queue<E> {
    // 阻塞插入:如果队列满,则阻塞等待
    void put(E e) throws InterruptedException;
    
    // 阻塞移除:如果队列空,则阻塞等待
    E take() throws InterruptedException;
}
3.4.2 非阻塞方法
java 复制代码
// 非阻塞插入:如果队列满,返回false
boolean offer(E e);

// 非阻塞移除:如果队列空,返回null
E poll();
3.4.3 超时方法
java 复制代码
// 超时插入:如果队列满,等待指定时间
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;

// 超时移除:如果队列空,等待指定时间
E poll(long timeout, TimeUnit unit) throws InterruptedException;
3.4.4 各种BlockingQueue实现

1. ArrayBlockingQueue

基于数组的有界阻塞队列:

java 复制代码
// 创建容量为10的阻塞队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

// 生产者
queue.put("数据");

// 消费者
String data = queue.take();

2. LinkedBlockingQueue

基于链表的有界或无界阻塞队列:

java 复制代码
// 有界队列
BlockingQueue<String> queue1 = new LinkedBlockingQueue<>(100);

// 无界队列
BlockingQueue<String> queue2 = new LinkedBlockingQueue<>();

3. PriorityBlockingQueue

具有优先级的无界阻塞队列:

java 复制代码
// 创建优先级队列
BlockingQueue<Task> queue = new PriorityBlockingQueue<>(
    10, 
    (t1, t2) -> t1.getPriority() - t2.getPriority()
);

4. DelayQueue

延迟队列,元素只有在延迟时间到达后才能被取出:

java 复制代码
// 创建延迟队列
DelayQueue<DelayedTask> queue = new DelayQueue<>();

// 添加延迟任务
queue.put(new DelayedTask("任务1", 5, TimeUnit.SECONDS));

// 取出到期的任务
DelayedTask task = queue.take();

5. SynchronousQueue

同步队列,不存储元素,每个插入操作必须等待另一个线程的移除操作:

java 复制代码
// 创建同步队列
BlockingQueue<String> queue = new SynchronousQueue<>();

// 生产者线程
new Thread(() -> {
    try {
        queue.put("数据");
        System.out.println("数据已放入");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

// 消费者线程
new Thread(() -> {
    try {
        String data = queue.take();
        System.out.println("取出数据:" + data);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

3.5 生产者-消费者模式示例

使用BlockingQueue实现生产者-消费者模式:

java 复制代码
// 创建阻塞队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

// 生产者
class Producer implements Runnable {
    private BlockingQueue<String> queue;
    
    public Producer(BlockingQueue<String> queue) {
        this.queue = queue;
    }
    
    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                String item = "Item-" + i;
                queue.put(item);  // 阻塞插入
                System.out.println("生产:" + item);
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 消费者
class Consumer implements Runnable {
    private BlockingQueue<String> queue;
    
    public Consumer(BlockingQueue<String> queue) {
        this.queue = queue;
    }
    
    @Override
    public void run() {
        try {
            while (true) {
                String item = queue.take();  // 阻塞取出
                System.out.println("消费:" + item);
                Thread.sleep(200);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 使用示例
public class ProducerConsumerDemo {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
        
        // 启动生产者
        new Thread(new Producer(queue)).start();
        
        // 启动消费者
        new Thread(new Consumer(queue)).start();
    }
}

4. 无锁编程

4.1 无锁编程概述

4.1.1 什么是无锁编程

无锁编程(Lock-Free Programming)是一种并发编程技术,它不使用传统的锁机制(如synchronized、ReentrantLock等),而是通过原子操作(如CAS)来实现线程安全。

无锁编程的核心思想:

  • 使用原子操作(CAS)来保证数据的一致性
  • 通过自旋重试来处理竞争
  • 避免线程阻塞和上下文切换

无锁编程的优势:

  • 高性能:避免了锁带来的阻塞和上下文切换开销
  • 可扩展性好:不会因为锁竞争导致性能下降
  • 避免死锁:没有锁,自然不会有死锁问题
  • 响应性好:线程不会被阻塞,可以快速响应

无锁编程的挑战:

  • 实现复杂:需要仔细设计算法,确保正确性
  • ABA问题:需要处理ABA问题(使用版本号或时间戳)
  • 自旋开销:在高竞争情况下,自旋会消耗CPU资源
  • 内存可见性:需要正确使用volatile或内存屏障
4.1.2 无锁编程 vs 有锁编程

有锁编程(传统方式):

java 复制代码
// 使用synchronized
public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;  // 需要获取锁才能执行
    }
    
    public synchronized int get() {
        return count;  // 需要获取锁才能读取
    }
}

无锁编程(CAS方式):

java 复制代码
// 使用AtomicInteger(内部使用CAS)
public class Counter {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // 使用CAS,无需锁
    }
    
    public int get() {
        return count.get();  // 直接读取,无需锁
    }
}

性能对比:

  • 低竞争场景:无锁编程性能略好
  • 高竞争场景:无锁编程性能明显更好(避免阻塞)
  • 极端竞争场景:有锁编程可能更好(避免CPU空转)

4.2 CAS无锁算法

4.2.1 CAS操作详解

CAS(Compare-And-Swap)是原子操作,用于实现无锁编程。

CAS操作的定义:

java 复制代码
// CAS操作的伪代码
boolean compareAndSwap(内存地址, 期望值, 新值) {
    if (内存地址的值 == 期望值) {
        内存地址的值 = 新值;
        return true;
    } else {
        return false;
    }
}

Java中的CAS操作:

java 复制代码
// AtomicInteger的CAS操作
public class AtomicInteger {
    private volatile int value;
    
    // CAS操作:如果当前值是expect,则更新为update
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    
    // 自增操作(使用CAS实现)
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
            // CAS失败,重试
        }
    }
}

CAS操作的特点:

  • 原子性:CAS操作是原子的,不会被中断
  • 乐观锁:假设没有竞争,如果失败就重试
  • 无阻塞:不会导致线程阻塞
4.2.2 自旋锁

自旋锁是一种基于CAS的锁实现,线程在获取锁失败时会自旋(循环重试),而不是阻塞。

自旋锁的实现:

java 复制代码
public class SpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<>();
    
    // 获取锁
    public void lock() {
        Thread current = Thread.currentThread();
        // 自旋等待,直到成功获取锁
        while (!owner.compareAndSet(null, current)) {
            // 自旋:空循环,等待锁释放
        }
    }
    
    // 释放锁
    public void unlock() {
        Thread current = Thread.currentThread();
        // 只有持有锁的线程才能释放
        owner.compareAndSet(current, null);
    }
}

自旋锁的使用:

java 复制代码
SpinLock lock = new SpinLock();

// 线程1
new Thread(() -> {
    lock.lock();
    try {
        // 临界区代码
        System.out.println("线程1执行");
    } finally {
        lock.unlock();
    }
}).start();

// 线程2
new Thread(() -> {
    lock.lock();
    try {
        // 临界区代码
        System.out.println("线程2执行");
    } finally {
        lock.unlock();
    }
}).start();

自旋锁的优缺点:

  • 优点:避免线程阻塞和上下文切换,响应快
  • 缺点:占用CPU资源,不适合长时间持有锁的场景
4.2.3 无锁栈

使用CAS实现的无锁栈:

java 复制代码
public class LockFreeStack<E> {
    // 栈顶节点
    private AtomicReference<Node<E>> top = new AtomicReference<>();
    
    // 节点类
    private static class Node<E> {
        E item;
        Node<E> next;
        
        Node(E item) {
            this.item = item;
        }
    }
    
    // 入栈
    public void push(E item) {
        Node<E> newHead = new Node<>(item);
        Node<E> oldHead;
        do {
            oldHead = top.get();
            newHead.next = oldHead;
        } while (!top.compareAndSet(oldHead, newHead));
    }
    
    // 出栈
    public E pop() {
        Node<E> oldHead;
        Node<E> newHead;
        do {
            oldHead = top.get();
            if (oldHead == null) {
                return null;  // 栈为空
            }
            newHead = oldHead.next;
        } while (!top.compareAndSet(oldHead, newHead));
        return oldHead.item;
    }
}

使用示例:

java 复制代码
LockFreeStack<String> stack = new LockFreeStack<>();

// 多个线程同时操作栈
for (int i = 0; i < 10; i++) {
    final int index = i;
    new Thread(() -> {
        stack.push("Item-" + index);
        String item = stack.pop();
        System.out.println("弹出:" + item);
    }).start();
}
4.2.4 无锁队列

使用CAS实现的无锁队列(简化版):

java 复制代码
public class LockFreeQueue<E> {
    // 头节点(哨兵节点)
    private final Node<E> dummy = new Node<>(null);
    private AtomicReference<Node<E>> head = new AtomicReference<>(dummy);
    private AtomicReference<Node<E>> tail = new AtomicReference<>(dummy);
    
    // 节点类
    private static class Node<E> {
        volatile E item;
        AtomicReference<Node<E>> next = new AtomicReference<>();
        
        Node(E item) {
            this.item = item;
        }
    }
    
    // 入队
    public void enqueue(E item) {
        Node<E> newNode = new Node<>(item);
        Node<E> prevTail;
        Node<E> prevTailNext;
        
        while (true) {
            prevTail = tail.get();
            prevTailNext = prevTail.next.get();
            
            // 检查tail是否被其他线程更新
            if (prevTail == tail.get()) {
                if (prevTailNext == null) {
                    // 尝试将新节点链接到队尾
                    if (prevTail.next.compareAndSet(null, newNode)) {
                        // 更新tail指针
                        tail.compareAndSet(prevTail, newNode);
                        return;
                    }
                } else {
                    // 帮助其他线程更新tail
                    tail.compareAndSet(prevTail, prevTailNext);
                }
            }
        }
    }
    
    // 出队
    public E dequeue() {
        Node<E> headNode;
        Node<E> nextNode;
        
        while (true) {
            headNode = head.get();
            nextNode = headNode.next.get();
            
            if (headNode == head.get()) {
                if (nextNode == null) {
                    return null;  // 队列为空
                }
                // 尝试更新head指针
                if (head.compareAndSet(headNode, nextNode)) {
                    return nextNode.item;
                }
            }
        }
    }
}

使用示例:

java 复制代码
LockFreeQueue<String> queue = new LockFreeQueue<>();

// 生产者线程
new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        queue.enqueue("Item-" + i);
        System.out.println("入队:Item-" + i);
    }
}).start();

// 消费者线程
new Thread(() -> {
    for (int i = 0; i < 10; i++) {
        String item = queue.dequeue();
        if (item != null) {
            System.out.println("出队:" + item);
        }
    }
}).start();

4.3 无锁数据结构

4.3.1 无锁链表

无锁链表的实现比较复杂,这里给出一个简化版本:

java 复制代码
public class LockFreeLinkedList<E> {
    private AtomicReference<Node<E>> head = new AtomicReference<>();
    
    private static class Node<E> {
        E item;
        AtomicReference<Node<E>> next = new AtomicReference<>();
        volatile boolean marked = false;  // 标记节点是否被删除
        
        Node(E item) {
            this.item = item;
        }
    }
    
    // 添加元素
    public void add(E item) {
        Node<E> newNode = new Node<>(item);
        Node<E> current = head.get();
        
        while (true) {
            if (current == null) {
                if (head.compareAndSet(null, newNode)) {
                    return;
                }
                current = head.get();
            } else {
                Node<E> next = current.next.get();
                if (next == null) {
                    if (current.next.compareAndSet(null, newNode)) {
                        return;
                    }
                } else {
                    current = next;
                }
            }
        }
    }
    
    // 查找元素
    public boolean contains(E item) {
        Node<E> current = head.get();
        while (current != null) {
            if (!current.marked && current.item.equals(item)) {
                return true;
            }
            current = current.next.get();
        }
        return false;
    }
}
4.3.2 无锁哈希表

无锁哈希表的实现非常复杂,通常使用分段锁或CAS来实现。这里给出一个概念性的说明:

java 复制代码
// 概念性实现(实际实现更复杂)
public class LockFreeHashMap<K, V> {
    private AtomicReferenceArray<Node<K, V>> buckets;
    
    private static class Node<K, V> {
        K key;
        V value;
        AtomicReference<Node<K, V>> next = new AtomicReference<>();
        
        Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }
    
    // put操作(简化版)
    public void put(K key, V value) {
        int hash = key.hashCode();
        int index = hash % buckets.length();
        Node<K, V> newNode = new Node<>(key, value);
        
        while (true) {
            Node<K, V> head = buckets.get(index);
            if (head == null) {
                if (buckets.compareAndSet(index, null, newNode)) {
                    return;
                }
                head = buckets.get(index);
            } else {
                // 遍历链表,查找或插入
                // ... 复杂的CAS操作
            }
        }
    }
}

注意: 实际的无锁哈希表实现非常复杂,JDK的ConcurrentHashMap使用了分段锁+CAS的混合方式。

4.4 内存屏障

4.4.1 内存屏障的作用

内存屏障(Memory Barrier)是一种CPU指令,用于控制内存操作的顺序和可见性。

内存屏障的作用:

  • 防止指令重排序:确保屏障前后的指令不会被重排序
  • 保证内存可见性:确保屏障前的写操作对屏障后的读操作可见
  • 保证原子性:某些内存屏障可以保证操作的原子性

内存屏障的类型:

  • Load Barrier(读屏障):确保屏障前的读操作完成
  • Store Barrier(写屏障):确保屏障前的写操作完成
  • Full Barrier(全屏障):确保屏障前的所有操作完成
4.4.2 Java中的内存屏障

Java通过volatile关键字和synchronized关键字来使用内存屏障:

java 复制代码
// volatile使用内存屏障
public class Example {
    private volatile int value = 0;
    
    public void write() {
        value = 1;  // Store Barrier:确保写操作完成
    }
    
    public int read() {
        return value;  // Load Barrier:确保读操作看到最新值
    }
}

volatile的内存语义:

  • 写操作:在写操作后插入Store Barrier,确保写操作立即刷新到主内存
  • 读操作:在读操作前插入Load Barrier,确保从主内存读取最新值

synchronized的内存语义:

  • 进入同步块:插入Load Barrier,确保看到最新值
  • 退出同步块:插入Store Barrier,确保写操作刷新到主内存
4.4.3 内存屏障的实际应用
java 复制代码
// 使用volatile保证可见性
public class VisibilityExample {
    private volatile boolean flag = false;
    
    // 线程1:设置标志
    public void setFlag() {
        flag = true;  // Store Barrier:确保写操作完成
    }
    
    // 线程2:读取标志
    public boolean getFlag() {
        return flag;  // Load Barrier:确保读取最新值
    }
}

// 使用synchronized保证可见性和原子性
public class SynchronizedExample {
    private int count = 0;
    
    public synchronized void increment() {
        count++;  // 进入同步块:Load Barrier
                  // 退出同步块:Store Barrier
    }
    
    public synchronized int get() {
        return count;  // 进入同步块:Load Barrier
    }
}

5. 性能优化

5.1 锁优化

5.1.1 减少锁的持有时间

锁的持有时间越短,其他线程等待的时间就越短,性能就越好。

不好的做法:

java 复制代码
public class BadExample {
    private final Object lock = new Object();
    
    public void process() {
        synchronized (lock) {
            // 执行一些不需要同步的操作
            doSomething1();  // 不需要锁
            doSomething2();  // 不需要锁
            
            // 只有这里需要同步
            sharedResource.update();
            
            // 执行一些不需要同步的操作
            doSomething3();  // 不需要锁
            doSomething4();  // 不需要锁
        }
    }
}

好的做法:

java 复制代码
public class GoodExample {
    private final Object lock = new Object();
    
    public void process() {
        // 执行不需要同步的操作
        doSomething1();
        doSomething2();
        
        // 只在需要同步的地方加锁
        synchronized (lock) {
            sharedResource.update();
        }
        
        // 执行不需要同步的操作
        doSomething3();
        doSomething4();
    }
}

性能提升: 锁的持有时间从整个方法减少到只有关键操作,其他线程的等待时间大大减少。

5.1.2 减小锁的粒度

使用多个细粒度的锁,而不是一个粗粒度的大锁。

不好的做法:

java 复制代码
public class BadExample {
    private final Object lock = new Object();
    private Map<String, String> map1 = new HashMap<>();
    private Map<String, String> map2 = new HashMap<>();
    private List<String> list = new ArrayList<>();
    
    public void updateMap1(String key, String value) {
        synchronized (lock) {  // 锁住所有资源
            map1.put(key, value);
        }
    }
    
    public void updateMap2(String key, String value) {
        synchronized (lock) {  // 锁住所有资源
            map2.put(key, value);
        }
    }
    
    public void updateList(String item) {
        synchronized (lock) {  // 锁住所有资源
            list.add(item);
        }
    }
}

好的做法:

java 复制代码
public class GoodExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    private final Object lock3 = new Object();
    private Map<String, String> map1 = new HashMap<>();
    private Map<String, String> map2 = new HashMap<>();
    private List<String> list = new ArrayList<>();
    
    public void updateMap1(String key, String value) {
        synchronized (lock1) {  // 只锁map1
            map1.put(key, value);
        }
    }
    
    public void updateMap2(String key, String value) {
        synchronized (lock2) {  // 只锁map2
            map2.put(key, value);
        }
    }
    
    public void updateList(String item) {
        synchronized (lock3) {  // 只锁list
            list.add(item);
        }
    }
}

性能提升: 不同资源的操作可以并发执行,不会相互阻塞。

5.1.3 锁分离

将读写操作分离,使用读写锁。

不好的做法:

java 复制代码
public class BadExample {
    private final Object lock = new Object();
    private Map<String, String> map = new HashMap<>();
    
    public String get(String key) {
        synchronized (lock) {  // 读操作也需要锁
            return map.get(key);
        }
    }
    
    public void put(String key, String value) {
        synchronized (lock) {  // 写操作需要锁
            map.put(key, value);
        }
    }
}

好的做法:

java 复制代码
public class GoodExample {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();
    private Map<String, String> map = new HashMap<>();
    
    public String get(String key) {
        readLock.lock();  // 读锁:多个读操作可以并发
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }
    
    public void put(String key, String value) {
        writeLock.lock();  // 写锁:独占
        try {
            map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
}

性能提升: 多个读操作可以并发执行,大大提高读多写少场景的性能。

5.1.4 锁粗化

在某些情况下,将多个连续的锁操作合并为一个,可以减少锁的获取和释放开销。

不好的做法:

java 复制代码
public class BadExample {
    private final Object lock = new Object();
    
    public void process() {
        for (int i = 0; i < 1000; i++) {
            synchronized (lock) {  // 每次循环都获取和释放锁
                sharedResource.update(i);
            }
        }
    }
}

好的做法:

java 复制代码
public class GoodExample {
    private final Object lock = new Object();
    
    public void process() {
        synchronized (lock) {  // 只获取一次锁
            for (int i = 0; i < 1000; i++) {
                sharedResource.update(i);
            }
        }
    }
}

注意: 锁粗化需要谨慎使用,只有在确定连续操作都需要同步时才使用。

5.1.5 无锁编程

使用无锁数据结构,避免锁的开销。

java 复制代码
// 使用AtomicInteger代替synchronized
public class LockFreeExample {
    private AtomicInteger count = new AtomicInteger(0);
    
    public void increment() {
        count.incrementAndGet();  // 无锁操作
    }
    
    public int get() {
        return count.get();  // 无锁操作
    }
}

// 使用ConcurrentHashMap代替synchronized HashMap
public class ConcurrentExample {
    private ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
    
    public void put(String key, String value) {
        map.put(key, value);  // 内部使用CAS,无锁
    }
    
    public String get(String key) {
        return map.get(key);  // 无锁读取
    }
}

5.2 线程池优化

5.2.1 合理设置线程数

线程数的设置需要根据任务类型来决定:

java 复制代码
// CPU密集型任务
int cpuCount = Runtime.getRuntime().availableProcessors();
int threadCount = cpuCount + 1;  // CPU核心数 + 1

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    threadCount, threadCount, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);

// IO密集型任务
int threadCount = cpuCount * 2;  // CPU核心数 * 2

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    threadCount, threadCount * 2, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);
5.2.2 选择合适的队列

根据任务的特点选择合适的队列:

java 复制代码
// 任务数量可预估,使用有界队列
ThreadPoolExecutor executor1 = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new ArrayBlockingQueue<>(100)  // 有界队列
);

// 需要优先级,使用优先级队列
ThreadPoolExecutor executor2 = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new PriorityBlockingQueue<>(100, 
        (r1, r2) -> ((Task) r1).getPriority() - ((Task) r2).getPriority())
    )
);
5.2.3 监控和调优

定期监控线程池的状态,及时调整参数:

java 复制代码
public class ThreadPoolMonitor {
    private ThreadPoolExecutor executor;
    
    public void monitor() {
        System.out.println("核心线程数: " + executor.getCorePoolSize());
        System.out.println("当前线程数: " + executor.getPoolSize());
        System.out.println("活跃线程数: " + executor.getActiveCount());
        System.out.println("队列大小: " + executor.getQueue().size());
        System.out.println("已完成任务数: " + executor.getCompletedTaskCount());
        
        // 如果队列积压严重,考虑增加线程数或队列容量
        if (executor.getQueue().size() > 1000) {
            System.out.println("警告:队列积压严重!");
        }
    }
}

5.3 并发集合优化

5.3.1 选择合适的并发集合

根据使用场景选择合适的并发集合:

java 复制代码
// 读多写少:使用ConcurrentHashMap
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

// 写多读少:使用CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

// 需要阻塞操作:使用BlockingQueue
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);
5.3.2 避免不必要的同步

不要在不必要的地方使用同步:

java 复制代码
// 不好的做法:使用同步集合
List<String> list = Collections.synchronizedList(new ArrayList<>());

// 好的做法:使用并发集合
List<String> list = new CopyOnWriteArrayList<>();

// 或者:如果不需要线程安全,直接使用普通集合
List<String> list = new ArrayList<>();  // 单线程使用

5.4 性能测试

5.4.1 基准测试

使用JMH(Java Microbenchmark Harness)进行基准测试:

java 复制代码
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class PerformanceTest {
    
    @Benchmark
    public void testSynchronized() {
        synchronized (this) {
            // 测试代码
        }
    }
    
    @Benchmark
    public void testLockFree() {
        // 测试无锁实现
    }
}
5.4.2 压力测试

使用工具进行压力测试:

java 复制代码
public class StressTest {
    public void test() {
        ExecutorService executor = Executors.newFixedThreadPool(100);
        CountDownLatch latch = new CountDownLatch(1000);
        
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> {
                // 执行测试代码
                latch.countDown();
            });
        }
        
        latch.await();
        long end = System.currentTimeMillis();
        System.out.println("总耗时: " + (end - start) + "ms");
    }
}
5.4.3 性能分析工具

使用性能分析工具找出性能瓶颈:

  • JProfiler:商业工具,功能强大
  • VisualVM:免费工具,JDK自带
  • JConsole:JDK自带,简单易用

6. 并发问题诊断

6.1 死锁

6.1.1 死锁的定义

死锁是指两个或多个线程互相等待对方持有的资源,导致所有线程都无法继续执行。

死锁的典型场景:

java 复制代码
// 线程1持有锁A,等待锁B
// 线程2持有锁B,等待锁A
// 结果:两个线程都无法继续执行

public class DeadlockExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {
            System.out.println("线程1持有lock1");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock2) {  // 等待lock2
                System.out.println("线程1持有lock2");
            }
        }
    }
    
    public void method2() {
        synchronized (lock2) {
            System.out.println("线程2持有lock2");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock1) {  // 等待lock1
                System.out.println("线程2持有lock1");
            }
        }
    }
    
    public static void main(String[] args) {
        DeadlockExample example = new DeadlockExample();
        
        // 线程1执行method1
        new Thread(() -> example.method1()).start();
        
        // 线程2执行method2
        new Thread(() -> example.method2()).start();
        
        // 结果:两个线程互相等待,发生死锁
    }
}
6.1.2 死锁产生的条件

死锁产生的四个必要条件(必须同时满足):

  1. 互斥条件:资源不能被多个线程同时使用
  2. 请求与保持条件:线程持有资源的同时请求其他资源
  3. 不剥夺条件:线程已获得的资源不能被其他线程强制释放
  4. 循环等待条件:存在一个循环等待链
6.1.3 死锁的检测

使用jstack检测死锁:

bash 复制代码
# 1. 找到Java进程ID
jps

# 2. 生成线程堆栈
jstack <pid>

# 3. 查找死锁信息
# 输出中会显示:
# Found one Java-level deadlock:
# ...

使用代码检测死锁:

java 复制代码
// 使用ThreadMXBean检测死锁
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadBean.findDeadlockedThreads();

if (deadlockedThreads != null) {
    ThreadInfo[] threadInfos = threadBean.getThreadInfo(deadlockedThreads);
    System.out.println("检测到死锁:");
    for (ThreadInfo threadInfo : threadInfos) {
        System.out.println(threadInfo.getThreadName());
    }
}
6.1.4 死锁的避免

1. 避免嵌套锁

java 复制代码
// 不好的做法:嵌套锁容易导致死锁
synchronized (lock1) {
    synchronized (lock2) {
        // 代码
    }
}

// 好的做法:避免嵌套,或者使用锁顺序

2. 使用锁顺序

java 复制代码
// 确保所有线程以相同的顺序获取锁
public class LockOrderExample {
    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    
    public void method1() {
        synchronized (lock1) {  // 先获取lock1
            synchronized (lock2) {  // 再获取lock2
                // 代码
            }
        }
    }
    
    public void method2() {
        synchronized (lock1) {  // 先获取lock1(与method1顺序一致)
            synchronized (lock2) {  // 再获取lock2
                // 代码
            }
        }
    }
}

3. 使用超时锁

java 复制代码
// 使用tryLock()设置超时
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

public void method() {
    try {
        if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
            try {
                if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
                    try {
                        // 代码
                    } finally {
                        lock2.unlock();
                    }
                }
            } finally {
                lock1.unlock();
            }
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
6.1.5 死锁的预防

1. 破坏请求与保持条件

java 复制代码
// 一次性获取所有需要的锁
public void method() {
    synchronized (lock1) {
        synchronized (lock2) {
            // 一次性获取所有锁
        }
    }
}

2. 破坏不剥夺条件

java 复制代码
// 使用可中断的锁
ReentrantLock lock = new ReentrantLock();

public void method() {
    try {
        lock.lockInterruptibly();  // 可中断
        // 代码
    } catch (InterruptedException e) {
        // 处理中断
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

3. 破坏循环等待条件

java 复制代码
// 使用锁顺序,避免循环等待
// 所有线程按照相同的顺序获取锁
6.1.6 死锁的恢复

死锁恢复通常需要人工干预或系统重启:

java 复制代码
// 检测到死锁后,可以尝试中断线程
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadBean.findDeadlockedThreads();

if (deadlockedThreads != null) {
    ThreadInfo[] threadInfos = threadBean.getThreadInfo(deadlockedThreads);
    for (ThreadInfo threadInfo : threadInfos) {
        // 尝试中断死锁线程(可能无效)
        Thread thread = findThread(threadInfo.getThreadId());
        if (thread != null) {
            thread.interrupt();
        }
    }
}

6.2 活锁

6.2.1 活锁的定义

活锁是指线程虽然没有被阻塞,但是由于不断重试相同的操作,导致无法继续执行。

活锁的示例:

java 复制代码
public class LivelockExample {
    private boolean flag = true;
    
    public void method1() {
        while (flag) {
            // 等待flag变为false
            if (!flag) {
                flag = true;  // 又设置为true
                break;
            }
        }
    }
    
    public void method2() {
        while (!flag) {
            // 等待flag变为true
            if (flag) {
                flag = false;  // 又设置为false
                break;
            }
        }
    }
    
    // 两个线程不断互相修改flag,导致活锁
}
6.2.2 活锁的解决

1. 引入随机性

java 复制代码
// 在重试时引入随机延迟
public void method() {
    Random random = new Random();
    while (condition) {
        if (tryOperation()) {
            break;
        }
        // 随机延迟,避免同时重试
        try {
            Thread.sleep(random.nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. 使用退避策略

java 复制代码
// 使用指数退避
public void method() {
    int delay = 1;
    while (condition) {
        if (tryOperation()) {
            break;
        }
        try {
            Thread.sleep(delay);
            delay = Math.min(delay * 2, 1000);  // 指数退避,最大1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6.3 饥饿

6.3.1 饥饿的定义

饥饿是指某些线程由于优先级低或资源竞争激烈,长时间无法获得执行机会。

饥饿的示例:

java 复制代码
// 高优先级线程一直占用CPU,低优先级线程无法执行
public class StarvationExample {
    public static void main(String[] args) {
        // 高优先级线程
        Thread highPriority = new Thread(() -> {
            while (true) {
                // 一直执行,占用CPU
            }
        });
        highPriority.setPriority(Thread.MAX_PRIORITY);
        highPriority.start();
        
        // 低优先级线程
        Thread lowPriority = new Thread(() -> {
            System.out.println("低优先级线程执行");
        });
        lowPriority.setPriority(Thread.MIN_PRIORITY);
        lowPriority.start();
        
        // 低优先级线程可能一直无法执行
    }
}
6.3.2 饥饿的原因
  1. 线程优先级设置不当:高优先级线程一直占用CPU
  2. 锁竞争激烈:某些线程一直无法获取锁
  3. 资源分配不公平:某些线程总是优先获得资源
6.3.3 饥饿的解决

1. 使用公平锁

java 复制代码
// 使用公平锁,确保线程按顺序获取锁
ReentrantLock lock = new ReentrantLock(true);  // 公平锁

public void method() {
    lock.lock();
    try {
        // 代码
    } finally {
        lock.unlock();
    }
}

2. 避免设置线程优先级

java 复制代码
// 不要依赖线程优先级,让操作系统公平调度
// 线程优先级在不同操作系统上表现不一致

3. 使用线程池

java 复制代码
// 使用线程池,确保所有任务都有执行机会
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>()
);

6.4 性能问题

6.4.1 CPU使用率过高

原因:

  • 线程过多,上下文切换频繁
  • 死循环或长时间计算
  • 锁竞争激烈,大量自旋

诊断方法:

java 复制代码
// 使用ThreadMXBean监控CPU使用
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
long[] allThreads = threadBean.getAllThreadIds();
ThreadInfo[] threadInfos = threadBean.getThreadInfo(allThreads);

for (ThreadInfo threadInfo : threadInfos) {
    long cpuTime = threadBean.getThreadCpuTime(threadInfo.getThreadId());
    System.out.println(threadInfo.getThreadName() + ": " + cpuTime + "ns");
}

解决方案:

  • 减少线程数量
  • 优化算法,减少计算量
  • 使用无锁数据结构,减少锁竞争
6.4.2 线程阻塞

原因:

  • 等待IO操作
  • 等待锁
  • 等待其他线程

诊断方法:

java 复制代码
// 使用jstack查看线程状态
// BLOCKED:等待获取锁
// WAITING:等待其他线程
// TIMED_WAITING:超时等待

解决方案:

  • 使用异步IO
  • 减少锁的持有时间
  • 使用超时机制
6.4.3 锁竞争激烈

原因:

  • 锁粒度太大
  • 线程数量过多
  • 锁持有时间过长

诊断方法:

java 复制代码
// 监控锁竞争情况
// 使用JProfiler或VisualVM查看锁竞争统计

解决方案:

  • 减小锁粒度
  • 使用读写锁
  • 使用无锁数据结构
6.4.4 上下文切换过多

原因:

  • 线程数量过多
  • 时间片设置不合理

诊断方法:

java 复制代码
// 使用系统工具查看上下文切换次数
// Linux: vmstat 1
// Windows: 性能监视器

解决方案:

  • 减少线程数量
  • 使用线程池
  • 合理设置线程优先级

7. 并发编程最佳实践

7.1 线程安全设计原则

7.1.1 优先使用不可变对象

不可变对象天然线程安全,因为对象创建后不能被修改,多个线程可以安全地共享。

不可变对象的特征:

  • 所有字段都是final的
  • 类本身是final的(防止被继承)
  • 没有提供修改对象状态的方法
  • 如果字段是引用类型,确保引用的对象也是不可变的

示例:

java 复制代码
// 不可变类
public final class ImmutablePoint {
    private final int x;
    private final int y;
    
    public ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() {
        return x;
    }
    
    public int getY() {
        return y;
    }
    
    // 如果需要修改,返回新对象
    public ImmutablePoint move(int dx, int dy) {
        return new ImmutablePoint(x + dx, y + dy);
    }
}

// 使用示例
ImmutablePoint point = new ImmutablePoint(10, 20);
// 多个线程可以安全地共享这个对象
// 不需要任何同步机制

优势:

  • 线程安全:不需要同步
  • 简单:不需要考虑并发问题
  • 性能好:不需要加锁
7.1.2 优先使用并发集合

优先使用并发集合,而不是同步包装的集合。

java 复制代码
// 推荐:使用并发集合
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
BlockingQueue<String> queue = new ArrayBlockingQueue<>(100);

优势: 性能好,锁粒度小,适合高并发场景。

7.1.3 合理使用锁

原则1:只在必要时使用锁

java 复制代码
// 只在需要同步的地方加锁
public void process() {
    doSomething1();  // 不需要同步
    
    synchronized (lock) {
        count++;  // 只有这里需要同步
    }
    
    doSomething2();  // 不需要同步
}

原则2:使用细粒度锁

java 复制代码
// 不同资源用不同的锁
private final Object lock1 = new Object();
private final Object lock2 = new Object();

public void update1() {
    synchronized (lock1) { /* 只锁资源1 */ }
}

public void update2() {
    synchronized (lock2) { /* 只锁资源2 */ }
}

原则3:读多写少用读写锁

java 复制代码
ReadWriteLock lock = new ReentrantReadWriteLock();

public String get(String key) {
    lock.readLock().lock();  // 读锁:多个线程可以并发读
    try {
        return map.get(key);
    } finally {
        lock.readLock().unlock();
    }
}
7.1.4 避免过度同步
java 复制代码
// 只在需要同步的地方加锁
public void method() {
    doSomething1();  // 不需要同步
    
    synchronized (lock) {
        sharedResource.update();  // 只有这里需要同步
    }
    
    doSomething2();  // 不需要同步
}

7.2 线程池使用最佳实践

7.2.1 合理配置参数

根据任务类型设置线程数:

java 复制代码
int cpuCount = Runtime.getRuntime().availableProcessors();

// CPU密集型:线程数 = CPU核心数 + 1
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    cpuCount + 1, cpuCount + 1, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);

// IO密集型:线程数 = CPU核心数 * 2
ThreadPoolExecutor executor2 = new ThreadPoolExecutor(
    cpuCount * 2, cpuCount * 4, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);
7.2.2 使用有意义的线程名称
java 复制代码
public class CustomThreadFactory implements ThreadFactory {
    private String namePrefix;
    private int number = 1;
    
    public Thread newThread(Runnable r) {
        return new Thread(r, namePrefix + "-" + number++);
    }
}

// 使用
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5, 10, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(100),
    new CustomThreadFactory("订单处理")
);
7.2.3 正确处理异常
java 复制代码
// 在任务内部捕获异常
executor.execute(() -> {
    try {
        processTask();
    } catch (Exception e) {
        System.err.println("任务异常: " + e.getMessage());
    }
});
7.2.4 优雅关闭线程池
java 复制代码
executor.shutdown();  // 停止接受新任务
try {
    if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
        executor.shutdownNow();  // 强制关闭
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
    Thread.currentThread().interrupt();
}

7.3 锁使用最佳实践

7.3.1 减少锁的持有时间
java 复制代码
// 只在需要的地方加锁
public void process() {
    doSomething1();  // 不需要同步
    
    synchronized (lock) {
        sharedResource.update();  // 只有这里需要同步
    }
    
    doSomething2();  // 不需要同步
}
7.3.2 避免嵌套锁
java 复制代码
// 所有方法按相同顺序获取锁,避免死锁
public void method1() {
    synchronized (lock1) {  // 先获取lock1
        synchronized (lock2) {  // 再获取lock2
            // 代码
        }
    }
}

public void method2() {
    synchronized (lock1) {  // 先获取lock1(顺序一致)
        synchronized (lock2) {  // 再获取lock2
            // 代码
        }
    }
}
7.3.3 使用读写锁

读多写少场景使用读写锁:

java 复制代码
ReadWriteLock lock = new ReentrantReadWriteLock();

public String get(String key) {
    lock.readLock().lock();  // 读锁:多个线程可并发
    try {
        return map.get(key);
    } finally {
        lock.readLock().unlock();
    }
}
7.3.4 避免死锁
java 复制代码
// 1. 使用锁顺序:所有线程按相同顺序获取锁
// 2. 使用超时锁:避免无限等待
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
        // 代码
    } finally {
        lock.unlock();
    }
}

7.4 性能优化最佳实践

7.4.1 减少上下文切换
java 复制代码
// 合理设置线程数,使用线程池
int threadCount = Runtime.getRuntime().availableProcessors() + 1;
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    threadCount, threadCount, 60L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000)
);
7.4.2 减少锁竞争
java 复制代码
// 使用细粒度锁、无锁数据结构、读写锁
private final Object lock1 = new Object();
AtomicInteger count = new AtomicInteger(0);
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
7.4.3 合理使用无锁编程
java 复制代码
// 使用Atomic类、ConcurrentHashMap等无锁数据结构
AtomicInteger count = new AtomicInteger(0);
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
7.4.4 监控和调优
java 复制代码
// 监控线程池状态
executor.getPoolSize();      // 当前线程数
executor.getActiveCount();   // 活跃线程数
executor.getQueue().size();  // 队列大小

8. 常见并发场景实现

8.1 单例模式(线程安全)

8.1.1 饿汉式
java 复制代码
public class Singleton1 {
    private static final Singleton1 instance = new Singleton1();
    private Singleton1() {}
    public static Singleton1 getInstance() {
        return instance;
    }
}
8.1.2 懒汉式(synchronized)
java 复制代码
public class Singleton2 {
    private static Singleton2 instance;
    public static synchronized Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}
8.1.3 双重检查锁定(DCL)
java 复制代码
public class Singleton3 {
    private static volatile Singleton3 instance;  // 必须volatile
    public static Singleton3 getInstance() {
        if (instance == null) {  // 第一次检查
            synchronized (Singleton3.class) {
                if (instance == null) {  // 第二次检查
                    instance = new Singleton3();
                }
            }
        }
        return instance;
    }
}
8.1.4 静态内部类(推荐)
java 复制代码
public class Singleton4 {
    private static class Holder {
        private static final Singleton4 instance = new Singleton4();
    }
    public static Singleton4 getInstance() {
        return Holder.instance;
    }
}
8.1.5 枚举方式(最简单)
java 复制代码
public enum Singleton5 {
    INSTANCE;
    public void doSomething() {
        // 方法
    }
}

8.2 生产者消费者

8.2.1 使用BlockingQueue(推荐)
java 复制代码
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

// 生产者
new Thread(() -> {
    queue.put("item");  // 阻塞插入
}).start();

// 消费者
new Thread(() -> {
    String item = queue.take();  // 阻塞取出
}).start();
8.2.2 使用wait/notify
java 复制代码
private Queue<String> queue = new LinkedList<>();
private final Object lock = new Object();

// 生产者
synchronized (lock) {
    while (queue.size() == MAX_SIZE) {
        lock.wait();  // 队列满,等待
    }
    queue.offer("item");
    lock.notifyAll();  // 通知消费者
}

// 消费者
synchronized (lock) {
    while (queue.isEmpty()) {
        lock.wait();  // 队列空,等待
    }
    String item = queue.poll();
    lock.notifyAll();  // 通知生产者
}
8.2.3 使用Lock/Condition
java 复制代码
Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

// 生产者
lock.lock();
try {
    while (queue.size() == MAX_SIZE) {
        notFull.await();  // 等待队列不满
    }
    queue.offer("item");
    notEmpty.signal();  // 通知消费者
} finally {
    lock.unlock();
}

8.3 多线程下载

8.3.1 文件分段下载
java 复制代码
// 1. 获取文件大小
long fileSize = getFileSize(url);
long segmentSize = fileSize / threadCount;

// 2. 创建文件
RandomAccessFile file = new RandomAccessFile(filePath, "rw");
file.setLength(fileSize);

// 3. 启动下载线程
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
    long start = i * segmentSize;
    long end = (i == threadCount - 1) ? fileSize - 1 : (i + 1) * segmentSize - 1;
    executor.submit(new DownloadTask(url, filePath, start, end, latch));
}
latch.await();
8.3.2 下载任务
java 复制代码
// 设置Range请求头,只下载指定范围
conn.setRequestProperty("Range", "bytes=" + start + "-" + end);

// 定位到文件位置并写入
file.seek(start);
file.write(buffer, 0, len);

8.4 限流器实现

8.4.1 令牌桶算法
java 复制代码
public class TokenBucket {
    private int capacity;
    private double refillRate;
    private double tokens;
    private long lastRefillTime;
    
    public synchronized boolean tryAcquire() {
        refill();  // 补充令牌
        if (tokens >= 1) {
            tokens -= 1;
            return true;
        }
        return false;
    }
    
    private void refill() {
        long now = System.currentTimeMillis();
        double elapsed = (now - lastRefillTime) / 1000.0;
        tokens = Math.min(capacity, tokens + elapsed * refillRate);
        lastRefillTime = now;
    }
}
8.4.2 漏桶算法
java 复制代码
public class LeakyBucket {
    private int capacity;
    private long leakInterval;
    private int water;
    private long lastLeakTime;
    
    public synchronized boolean tryAdd() {
        leak();  // 漏出
        if (water < capacity) {
            water++;
            return true;
        }
        return false;
    }
    
    private void leak() {
        long now = System.currentTimeMillis();
        int leaks = (int) ((now - lastLeakTime) / leakInterval);
        water = Math.max(0, water - leaks);
        lastLeakTime = now;
    }
}
8.4.3 滑动窗口算法
java 复制代码
public class SlidingWindow {
    private int windowSize;
    private int limit;
    private Queue<Long> requests = new LinkedList<>();
    
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        long windowStart = now - windowSize * 1000;
        
        // 移除过期请求
        while (!requests.isEmpty() && requests.peek() < windowStart) {
            requests.poll();
        }
        
        // 检查是否超限
        if (requests.size() < limit) {
            requests.offer(now);
            return true;
        }
        return false;
    }
}

8.5 缓存实现

8.5.1 线程安全的缓存
java 复制代码
ConcurrentHashMap<String, String> cache = new ConcurrentHashMap<>();
cache.put("key", "value");
String value = cache.get("key");
8.5.2 LRU缓存
java 复制代码
public class LRUCache<K, V> {
    private LinkedHashMap<K, V> cache;
    
    public LRUCache(int capacity) {
        this.cache = new LinkedHashMap<K, V>(capacity, 0.75f, true) {
            @Override
            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
                return size() > capacity;
            }
        };
    }
    
    public synchronized V get(K key) {
        return cache.get(key);
    }
    
    public synchronized void put(K key, V value) {
        cache.put(key, value);
    }
}
8.5.3 带过期时间的缓存
java 复制代码
private ConcurrentHashMap<K, V> cache = new ConcurrentHashMap<>();
private ConcurrentHashMap<K, Long> timestamps = new ConcurrentHashMap<>();
private long ttl;

public V get(K key) {
    Long timestamp = timestamps.get(key);
    if (timestamp != null && System.currentTimeMillis() - timestamp > ttl) {
        cache.remove(key);
        timestamps.remove(key);
        return null;
    }
    return cache.get(key);
}

9. 高并发系统设计

9.1 高并发系统架构

9.1.1 系统分层

高并发系统通常采用分层架构。

复制代码
┌─────────────────┐
│   负载均衡层     │  Nginx、F5等
├─────────────────┤
│   应用服务层     │  多个应用服务器
├─────────────────┤
│   缓存层         │  Redis、Memcached
├─────────────────┤
│   数据库层       │  主从复制、分库分表
└─────────────────┘

分层说明:

  • 负载均衡层:分发请求到多个应用服务器
  • 应用服务层:处理业务逻辑,可以水平扩展
  • 缓存层:减少数据库压力,提高响应速度
  • 数据库层:存储数据,使用主从复制和分库分表
9.1.2 负载均衡
java 复制代码
public class LoadBalancer {
    private List<String> servers;
    private int index = 0;
    
    public synchronized String getServer() {
        String server = servers.get(index);
        index = (index + 1) % servers.size();
        return server;
    }
}
9.1.3 缓存设计

Cache Aside模式:

java 复制代码
// 读:先查缓存,没有再查数据库
public Object get(String key) {
    Object value = cache.get(key);
    if (value == null) {
        value = database.get(key);
        cache.put(key, value);
    }
    return value;
}

// 写:更新数据库,删除缓存
public void update(String key, Object value) {
    database.update(key, value);
    cache.remove(key);
}
9.1.4 数据库优化
  • 主从复制:主库写,从库读
  • 分库分表:垂直分库(按业务),水平分表(按数据量)

9.2 并发控制策略

9.2.1 乐观锁
java 复制代码
// 更新时检查版本号
// SQL: UPDATE product SET stock = stock - ?, version = version + 1 
//      WHERE id = ? AND version = ?
int rows = productDao.updateStock(productId, quantity, oldVersion);
return rows > 0;  // 版本号匹配才更新成功
9.2.2 悲观锁
java 复制代码
// 查询时加锁
// SQL: SELECT * FROM product WHERE id = ? FOR UPDATE
Product product = productDao.selectForUpdate(productId);
// 其他线程无法修改,直到当前事务提交
9.2.3 分布式锁
java 复制代码
// Redis: SET key value NX EX 30
// 如果key不存在则设置,过期时间30秒
public boolean tryLock(String key) {
    return redis.setIfAbsent("lock:" + key, value, 30, TimeUnit.SECONDS);
}

public void unlock(String key) {
    redis.delete("lock:" + key);
}

9.3 限流与降级

9.3.1 限流算法

限流算法包括令牌桶、漏桶、滑动窗口等(详见8.4节)。

9.3.2 降级策略
java 复制代码
private int errorCount = 0;
private boolean degraded = false;

public void processRequest() {
    if (degraded) {
        returnDefaultResponse();  // 降级:返回默认值
        return;
    }
    
    try {
        doProcess();
        errorCount = 0;
    } catch (Exception e) {
        if (++errorCount > 10) {
            degraded = true;  // 触发降级
        }
    }
}
9.3.3 熔断机制
java 复制代码
private enum State { CLOSED, OPEN, HALF_OPEN }
private State state = State.CLOSED;
private int failureCount = 0;

public <T> T execute(Supplier<T> supplier) {
    if (state == State.OPEN) {
        throw new RuntimeException("熔断器打开");
    }
    
    try {
        T result = supplier.get();
        if (state == State.HALF_OPEN) {
            state = State.CLOSED;  // 恢复
        }
        failureCount = 0;
        return result;
    } catch (Exception e) {
        if (++failureCount >= 5) {
            state = State.OPEN;  // 熔断
        }
        throw e;
    }
}
相关推荐
Android-Flutter1 天前
android compose LazyColumn 垂直列表滚动 使用
android·kotlin
程序员Agions1 天前
Flutter 邪修秘籍:那些官方文档不会告诉你的骚操作
前端·flutter
白驹过隙不负青春1 天前
Docker-compose部署java服务及前端服务
java·运维·前端·docker·容器·centos
满天星辰1 天前
Vue.js的优点
前端·vue.js
用户74589002079541 天前
Handler机制
android
哒哒哒5285201 天前
React createContext 跨组件共享数据实战指南
前端
怪可爱的地球人1 天前
UnoCss最新配置攻略
前端
Carry3451 天前
Nexus respository 搭建前端 npm 私服
前端·docker
满天星辰1 天前
使用 onCleanup处理异步副作用
前端·vue.js