nbsp; } catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
//执行后钩子方法
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
在执行前后预留两个钩子空方法,留给子类来扩展,后文处理线程池异常也会用到
配置参数
线程池中是不是越多线程就越好呢?
首先,我们要明白创建线程是有开销的,程序计数器、虚拟机栈、本地方法栈都是线程私有的空间
并且线程在申请空间时,是通过CAS申请年轻代的Eden区中一块内存(因为可能存在多线程同时申请所以要CAS)
线程太多可能导致Eden空间被使用太多导致young gc,并且线程上下文切换也需要开销
因此,线程池中线程不是越多越好,行业内分为两种大概方案
针对CPU密集型,线程池设置最大线程数量为CPU核心数量+1,避免上下文切换,提高吞吐量,多留一个线程兜底
针对IO密集型,线程池设置最大线程数量为2倍CPU核心数量,由于IO需要等待,为了避免CPU空闲就多一些线程
具体业务场景需要具体分析,然后加上大量测试才能得到最合理的配置
Executor框架通过静态工厂方法提供几种线程池,比如:Executors.newSingleThreadExecutor()
、Executors.newFixedThreadPool()
、Executors.newCachedThreadPool()
但由于业务场景的不同,最好还是自定义线程池;当理解线程池参数和实现原理后,查看它们的源码并不难,我们不过多叙述
处理异常
线程池中如果出现异常会怎么样?
Runnable
当我们使用Runnable
任务时,出现异常会直接抛出
threadPool.execute(() -> {
int i = 1;
int j = 0;
System.out.println(i / j);
});
面对这种情况,我们可以在Runnable任务中使用try-catch进行捕获
threadPool.execute(() -> {
try {
int i = 1;
int j = 0;
System.out.println(i / j);
} catch (Exception e) {
System.out.println(e);
}
});
实际操作的话用日志记录哈,不要打印到控制台
Callable
当我们使用Callable
任务时,使用submit方法会获取Future
Future<Integer> future = threadPool.submit(() -> {
int i = 1;
int j = 0;
return i / j;
});
如果不使用Future.get()
去获取返回值,那么异常就不会抛出,这是比较危险的
为什么会出现这样的情况呢?
前文说过执行submit时会将Callable
封装成FutureTask
执行
在其实现Runnable中,在执行Callable任务时,如果出现异常会封装在FutureTask中
public void run() {
//...其他略
try {
//执行call任务
result = c.call();
ran = t