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()方法执行完成
状态转换流程:
- RUNNING → SHUTDOWN :调用
shutdown()方法 - RUNNING/SHUTDOWN → STOP :调用
shutdownNow()方法 - STOP → TIDYING:当线程池和队列都为空时
- SHUTDOWN → TIDYING:当线程池为空时
- 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()方法可以提交Runnable或Callable任务,并返回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类
Worker是ThreadPoolExecutor的内部类 ,用于封装工作线程。每个工作线程都对应一个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; // 被中断,重试
}
}
}
关键点(通俗理解):
-
核心线程 vs 非核心线程的获取方式:
- 核心线程 :使用
workQueue.take(),会一直阻塞等待,直到有任务 - 非核心线程 :使用
workQueue.poll(keepAliveTime),等待keepAliveTime时间,如果还没任务就返回null
- 核心线程 :使用
-
线程回收机制:
- 非核心线程如果超时(
keepAliveTime时间内没获取到任务),返回null - 返回null后,
runWorker()退出循环,线程被回收 - 核心线程默认不会超时,会一直等待任务
- 非核心线程如果超时(
-
线程池关闭时的处理:
- 如果线程池已关闭,
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(超时)
}
关键理解:
- Worker:是线程的"包装器",每个工作线程对应一个Worker
- runWorker():是线程的"工作循环",不断取任务、执行任务
- getTask():是"取任务"的方法,从队列中获取任务
- 这三个都是内部实现,我们平时使用线程池时不需要直接操作它们
为什么需要了解这些?
- 理解线程池的工作原理
- 调试线程池相关问题时有用
- 理解线程复用机制(一个线程执行多个任务)
- 理解线程回收机制(非核心线程超时回收)
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()方法
可以重写ThreadPoolExecutor的afterExecute()方法来统一处理异常:
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 并发集合的分类
- Map :
ConcurrentHashMap - List :
CopyOnWriteArrayList - Set :
CopyOnWriteArraySet、ConcurrentSkipListSet - Queue :
ConcurrentLinkedQueue、各种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;
}
}
核心机制:
- CAS操作:用于无锁的插入和更新
- synchronized锁:用于链表头节点,锁粒度小
- 分段锁思想:每个桶(数组元素)可以独立加锁
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()方法实现
ConcurrentHashMap的size()方法不是简单地返回一个变量,而是通过累加各个段的大小来计算:
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 死锁产生的条件
死锁产生的四个必要条件(必须同时满足):
- 互斥条件:资源不能被多个线程同时使用
- 请求与保持条件:线程持有资源的同时请求其他资源
- 不剥夺条件:线程已获得的资源不能被其他线程强制释放
- 循环等待条件:存在一个循环等待链
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 饥饿的原因
- 线程优先级设置不当:高优先级线程一直占用CPU
- 锁竞争激烈:某些线程一直无法获取锁
- 资源分配不公平:某些线程总是优先获得资源
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;
}
}