ThreadPoolExecutor代码见ThreadPoolExecutor部分
线程池的工作思路
如果当前运行的线程少于
corePoolSize
,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁如果运行的线程等于或多于
corePoolSize
,则将任务加入BlockingQueue如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)
如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用
RejectedExecutionHandler.rejectedExecution()
方法
任务提交思路
execute和submit两个方法:
execute()
方法用于提交不需要返回值的任务submit()
方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()
方法来获取返回值,get()
方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
线程池关闭
shutdown和shutdownNow方法:
它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止(例如下案例)。调用后线程池的isShutdown方法就会返回true
shutdownNow
首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程shutdown
只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
public class MyTest {
public static ExecutorService executor = Executors.newSingleThreadExecutor();
public static void main(String[] args) throws InterruptedException {
executor.execute(new Runnable() {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Interrupted, but running");
} else {
System.out.println("I'm running");
}
}
}
});
TimeUnit.SECONDS.sleep(3);
executor.shutdownNow();
TimeUnit.SECONDS.sleep(10);
}
}
合理配置线程池资源
对于任务性质分为cpu密集型和IO密集型
CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。
如果任务能拆分,且cpu部分和io部分耗时差不多,可用拆分成两个任务,能提升吞吐量;如果耗时差的大,则每笔要拆分
对于区分优先级的任务,可使用
PriorityBlockingQueue
,但是如果高优先级任务一直都在生成,可能出现低优先级任务不执行的风险依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越长,那么线程数应该设置得越大,这样才能更好地利用CPU
建议使用有界队列,防止因任务阻塞生产者无限提交任务导致爆内存
线程池监控
taskCount
:线程池需要执行的任务数量。completedTaskCount
:线程池在运行过程中已完成的任务数量,小于或等于taskCount。largestPoolSize
:线程池里曾经创建过的最大线程数量。通过这个数据可以知道线程池是否曾经满过。如该数值等于线程池的最大大小,则表示线程池曾经满过。getPoolSize
:线程池的线程数量。如果线程池不销毁的话,线程池里的线程不会自动销毁,所以这个大小只增不减。getActiveCount
:获取活动的线程数。
评论区