书接上文,【并发编程 | 第七篇】深入学习线程池(一)-优快云博客
没看过的可以先去看看我写的关于线程池的第一篇博客。
线程池的拒绝策略
线程池的拒绝策略有四种:
AbortPolicy:默认的拒绝策略,会抛 RejectedExecutionException 异常。
CallerRunsPolicy:让提交任务的线程自己来执行这个任务,也就是调用execute 方法的线程。
DiscardOldestPolicy:等待队列会丢弃队列中最老的一个任务,也就是队列中等待最久的任务,然后尝试重新提交被拒绝的任务。
DiscardPolicy:丢弃被拒绝的任务,不做任何处理也不抛出异常。
分别对应着你去银⾏办理业务被经理“薄纱”的四个场景:“我们系统瘫痪了”、“谁叫你来办的你找谁去”、“看你比较急,去队⾥加个塞”、“今天没办法,不行你看改⼀天”。
当线程池无法接受新的任务时,也就是线程数达到 maximumPoolSize,任务队列也满了的时候,就会触发拒绝策略。
如果默认策略不能满⾜需求,可以通过实现 RejectedExecutionHandler 接口来定义自己的淘汰策略。例如:记录 被拒绝任务的⽇志。
自定义拒绝策略:
class CustomRejectedHandler {
public static void main(String[] args) {
// ⾃定义拒绝策略
RejectedExecutionHandler rejectedHandler = (r, executor) -> {
System.out.println("Task " + r.toString() + " rejected. Queue size: "
+ executor.getQueue().size());
};
// ⾃定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核⼼线程数
4, // 最⼤线程数
10, // 空闲线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), // 阻塞队列容量
Executors.defaultThreadFactory(),
rejectedHandler // ⾃定义拒绝策略
);
for (int i = 0; i < 10; i++) {
final int taskNumber = i;
executor.execute(() -> {
System.out.println("Executing task " + taskNumber);
try {
Thread.sleep(1000); // 模拟任务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
线程池有哪几种阻塞队列呢?
常用的有五种:
有界队列 ArrayBlockingQueue;
无界队列 LinkedBlockingQueue;
优先级队列 PriorityBlockingQueue;
延迟队列 DelayQueue;
同步队列 SynchronousQueue。
1. ArrayBlockingQueue:一个有界的先进先出的阻塞队列,底层是一个数组,适合固定大小的线程池。
ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10, true);
2. LinkedBlockingQueue:底层是链表,如果不指定大小,默认大小是 Integer.MAX_VALUE,几乎相当于⼀个无界队列。
3.PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。任务按照其自然顺序或Comparator来排序。
适用于需要按照给定优先级处理任务的场景,比如优先处理紧急任务。
4.DelayQueue:类似于 PriorityBlockingQueue,由⼆叉堆实现的无界优先级阻塞队列。
5.SynchronousQueue::每个插⼊操作必须等待另⼀个线程的移除操作,同样,任何⼀个移除操作都必须等待另一个线程的插⼊操作。
线程池提交的execute和submit有什么区别
execute方法没有返回值,适用于不关心结果和异常的简单任务。
threadsPool.execute(new Runnable() {
@Override public void run() {
System.out.println("execute() ⽅法提交的任务");
}
})
submit 有返回值,适用于需要获取结果或处理异常的场景。
Future<Object> future = executor.submit(harReturnValuetask);
try { Object s = future.get(); }
catch (InterruptedException e | ExecutionException e) {
// 处理⽆法执⾏任务异常
} finally {
// 关闭线程池 executor.shutdown();
}
线程池如何关闭?
可以调用线程池的 shutdown 或 shutdownNow方法来关闭线程池。
shutdown不会立即停止线程池,而是等待所有任务执⾏完毕后再关闭线程池。
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(() -> System.out.println("Task 1"));
executor.execute(() -> System.out.println("Task 2"));
executor.shutdown(); // 不会⽴刻关闭,⽽是等待所有任务执⾏完毕
shutdownNow 会尝试通过⼀系列动作来停止线程池,包括停⽌接收外部提交的任务、忽略队列⾥等待的任务、尝试将正在跑的任务 interrupt 中断。
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.execute(() -> {
try {
Thread.sleep(5000); // 模拟⻓时间运⾏任务
System.out.println("Task executed");
}
catch (InterruptedException e) {
System.out.println("任务被中断");
}
});
List<Runnable> unexecutedTasks = executor.shutdownNow(); // ⽴即关闭线程池
System.out.println("未执⾏的任务数: " + unexecutedTasks.size());
需要注意的是,shutdownNow 不会真正终止正在运⾏的任务,只是给任务线程发送 interrupt 信号,任务是否能 真正终止取决于线程是否响应 InterruptedException。
线程池的线程数如何配置?
首先,分析任务是CPU密集型还是IO密集型。
1.如果是CPU密集型,我们应该尽量减少上下文切换,来优化CPU效率。一般来讲,核心线程数设置为处理器的核心数或核心数+1是较为理想的选择。
+1是为了以备不时之需,如果某线程因等待系统资源而阻塞时,可以有多余的线程顶上去,不至于影响整体性能。
2.对于IO密集型任务,由于线程经常处于等待状态,等待IO操作完成,所以我们可以设置更多的线程提高高并发,比如CPU核心数的两倍。
最后,我会根据业务需求和系统资源来调整线程池的其他参数,⽐如最⼤线程数、任务队列容量、非核⼼线程的空 闲存活时间等。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
cores, // 核⼼线程数设置为CPU核⼼数
cores * 2, // 最⼤线程数为核⼼数的两倍
60L, TimeUnit.SECONDS, // ⾮核⼼线程的空闲存活时间
new LinkedBlockingQueue<>(100) // 任务队列容量
)
有哪几种常见的线程池?
主要有四种:
固定大小的线程池 Executors.newFixedThreadPool(int nThreads); ,适合用于任务数量确定,且对线程数有 明确要求的场景。例如,IO 密集型任务、数据库连接池等。
缓存线程池 Executors.newCachedThreadPool(); ,适用于短时间内任务量波动较大的场景。例如,短时间内有大量的文件处理任务或网络请求。
定时任务线程池 Executors.newScheduledThreadPool(int corePoolSize); ,适用于需要定时执⾏任务的场景。例如,定时发送邮件、定时备份数据等。
单线程线程池 Executors.newSingleThreadExecutor(); ,适用于需要按顺序执⾏任务的场景。例如,⽇志记录、文件处理等
不管是 FixedThreadPool、CachedThreadPool,还是 SingleThreadExecutor 和 ScheduledThreadPoolExecutor,它们本质上都是 ThreadPoolExecutor 的不同配置。
线程池异常怎么处理?
常⻅的处理方式有,使用try-catch 捕获、使用Future 获取异常、自定义ThreadPoolExecutor 重写 afterExecute方法、使用 UncaughtExceptionHandler 捕获异常。
1.try-catch方法:
executor.execute(() -> {
try {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
}
catch (Exception e) {
System.err.println("捕获异常:" + e.getMessage());
}
});
2.使用Future获得异常:
Future<Object> future = executor.submit(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
return result;
});
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
System.err.println("捕获异常:" + e.getMessage());
}
3.自定义 ThreadPoolExecutor 重写 afterExecute方法。
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t != null) {
System.err.println("捕获异常:" + t.getMessage());
}
}
};
executor.execute(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
});
4.、使用UncaughtExceptionHandler 捕获异常。
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.setThreadFactory(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.err.println("捕获异常:" + e.getMessage());
}
});
return thread;
}
});
executor.execute(() -> {
System.out.println("任务开始");
int result = 1 / 0; // 除零异常
})
如果项⽬使用execute() ,不关⼼任务返回值,建议使用UncaughtExceptionHandler:
thread.setUncaughtExceptionHandler((t, e) ->
System.err.println("线程 " + t.getName() + " 捕获到异常:" + e.getMessage()))
如果项⽬使用submit() ,关⼼任务返回值,建议使用Future:
Future<?> future = executor.submit(task);
try {
future.get();
} catch (ExecutionException e) {
System.err.println("捕获异常:" + e.getCause());
}
如果想要全局捕获所有任务异常,建议重写 afterExecute ⽅法:
class MyThreadPoolExecutor extends ThreadPoolExecutor {
@Override
protected void afterExecute(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try { ((Future<?>) r).get(); } catch (Exception e) { System.err.println("任务
异常:" + e.getCause()); }
}
}
}
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!