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;
    }
}
相关推荐
海绵宝龙4 小时前
Vue中nextTick
前端·javascript·vue.js
天生欧皇张狗蛋5 小时前
前端部署path问题
前端
三川6985 小时前
面试题目记录
面试·职场和发展
Madison-No75 小时前
【Linux】文件操作&&重定向原理
android·linux·运维
H_z_q24015 小时前
Web前端制作一个评论发布案例
前端·javascript·css
2603_949462105 小时前
Flutter for OpenHarmony社团管理App实战:消息中心实现
android·javascript·flutter
秋秋小事5 小时前
可选链与非空操作符
前端
iRuriCatt6 小时前
智慧景区管理系统 | 计算机毕设项目
java·前端·spring boot·vue·毕设
andr_gale6 小时前
08_flutter中如何优雅的提前获取child的宽高
android·flutter
程序员清洒6 小时前
Flutter for OpenHarmony:Icon 与 IconButton — 图标系统集成
前端·学习·flutter·华为