Java提供了几种便捷的方法创建线程池,通过这些内置的api就能够很轻松的创建线程池。在java.util.concurrent包中的Executors类,其中的静态方法就是用来创建线程池的:
newFixedThreadPool():创建一个固定线程数量的线程池,而且线程池中的任务全部执行完成后,空闲的线程也不会被关闭。
newSingleThreadExecutor():创建一个只有一个线程的线程池,空闲时也不会被关闭。
newCachedThreadPool():创建一个可缓存的线程池,线程的数量为Integer.MAX_VALUE,空闲线程会临时缓存下来,线程会等待60s还是没有任务加入的话就会被关闭。
Executors类中还有一些创建线程池的方法(jdk8新加的),但是现在这个触极到我的知识盲区了~~
上面那几个方法,其实都是创建了一个ThreadPoolExecutor对象作为返回值,要搞清楚线程池的原理主要还是要分析ThreadPoolExecutor这个类。
ThreadPoolExecutor的构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ... }ThreadPoolExecutor的构造方法包含以下几个参数:
corePoolSize: 核心线程数量,常驻线程池中的线程,即时线程池中没有任务可执行,也不会被关闭。
maximumPoolSize:最大线程数量
keepAliveTime:空闲线程存活时间
unit: 空闲线程存活时间的单位
workQueue:工作队列,线程池一下忙不过来,那新来的任务就需要排队,排除中的任务就会放在workQueue中
threadFactory:线程工厂,创建线程用的
handler:RejectedExecutionHandler实例用于在线程池中没有空闲线程能够执行任务,并且workQueue中也容不下任务时拒绝任务时的策略。
ThreadPoolExecutor中的线程统称为工作线程,但有一个小概念是核心线程,核心线程由参数corePoolSize指定,如corePoolSize设置5,那线程池中就会有5条线程常驻线程池中,不会被回收掉,但是也会有例外,如果allowCoreThreadTimeOut为true空闲一段时间后,也会被关闭。
线程的状态和工作线程数量线程中的状态和工作线程和数量都是由ctl表示,是一个AtomicInteger类型的属性:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));ctl的高四位为线程的状态,其他位数为工作线程的数量,所以线程中最大的工作线程数量为(2^29)-1。
线程池中的状态有五种:
RUNNING:接收新的任务和处理队列中的任务
SHUTDOWN:不能新增任务,但是会继续处理已经添加的任务
STOP:不能新增任务,不会继续处理已经添加任务
TIDYING:所有的任务已经被终止,工作线程为0
TERMINATED:terminated()方法执行完成
状态码的定义如下:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); private static final int COUNT_BITS = Integer.SIZE - 3; private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits private static final int RUNNING = -1 << COUNT_BITS; private static final int SHUTDOWN = 0 << COUNT_BITS; private static final int STOP = 1 << COUNT_BITS; private static final int TIDYING = 2 << COUNT_BITS; private static final int TERMINATED = 3 << COUNT_BITS; 创建线程池如果有面试官问:如何正确的创建线程池?千万不要说使用Executors创建线程,虽然Executors能很方便的创建线程池,但是他提供的静态创建方法会有一些坑。
主要的原因是:maximumPoolSize和workQueue这两个参数
Executors静态方法在创建线程池时,如果maximumPoolSize设置为Integer.MAX_VALUE,这样会导致线程池可以一直要以接收运行任务,可能导致cpu负载过高。
workQueue是一个阻塞队列的实例,用于放置正在等待执行的任务。如果在创建线程种时workQueue实例没有指定任务的容量,那么等待队列中可以一直添加任务,极有可能导致oom。
所以创建线程,最好是根据线程池的用途,然后自己创建线程。
添加任务调用线程池的execute并不是立即执行任务,线程池内部用经过一顿操作,如:判断核心线程数、是否需要添加到等待队列中。
下来的代码是execute的源码,代码很简洁只有2个if语句:
public void execute(Runnable command) { int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } 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); } else if (!addWorker(command, false)) reject(command); }第一个if,如果当前线程池中的工作线程数量小于corePoolSize,直接创建一个工作线程执行任务
第二个if,当线程池处于运行状态,调用workQueue.offer(command)方法将任务添加到workQueue,否则调用addWorker(command, false)尝试去添加一个工作线程。
整理了一张图,把线程池分为三部分Core Worker、Worker、workQueue:
换一种说法,在调用execute方法时,任务首先会放在Core Worker内,然后才是workQueue,最后才会考虑Worker。