每当使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
在Java中通过线程池使线程可以复用,就是线程执行完一个任务,并不被销毁,而是可以继续执行其他的任务。
线程池的种类及使用场景:
线程池有四种:newCachedThreadPool、newFixedThreadPool、newSingleThreadExecutor、newScheduledThreadPool
1、Java中的ThreadPoolExecutor类
public class ThreadPoolExecutor extends AbstractExecutorService {
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;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
//任务缓存队列,用来存放等待执行的任务
private final BlockingQueue<Runnable> workQueue;
//线程池的主要状态锁,对线程池状态、线程池大小等的修改都要使用这个锁
private final ReentrantLock mainLock = new ReentrantLock();
//用来存放工作集,包含线程池中所有工作线程worker的集合
private final HashSet<Worker> workers = new HashSet<Worker>();
private final Condition termination = mainLock.newCondition();
//用来记录线程池中曾经出现过的最大线程数
private int largestPoolSize;
//用来记录已经执行完毕的任务个数
private long completedTaskCount;
//线程工厂,用来创建线程
private volatile ThreadFactory threadFactory;
//任务拒绝策略
private volatile RejectedExecutionHandler handler;
//线程存活时间
private volatile long keepAliveTime;
//是否允许为核心线程设置存活时间
private volatile boolean allowCoreThreadTimeOut;
//核心池的大小
private volatile int corePoolSize;
//线程池中最大能容纳的线程数
private volatile int maximumPoolSize;
//四个构造器(前三个都是在调用第四个)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize || keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null : AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
//execute方法是用来提交任务的
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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);
}
//shutdown()和shutdownNow()是用来关闭线程池的
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(SHUTDOWN);
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
//省略了其他属性和方法
}
线程池的状态:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
AtomicInteger类中有个int类型的value属性,它是一个原子整数,是一个复核类型的成员变量,代表了ThreadPoolExecutor中的控制状态,借助高低位包装了两个概念:
- workerCount:线程池中当前活动的线程数量,占据value的低29位;
- runState:线程池运行状态,占据value的高3位,有RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED五种状态。
一个int型变量是4个字节(即4byte),用32位二进制位表示。workerCount是线程池中当前活动的线程数量,占据AtomicInteger类中value属性的低29位,runState是线程池运行状态,占据AtomicInteger类中value属性的高3位。在ThreadPoolExecutor类中,使用COUNT_BITS变量表示workerCount所占的位数(private static final int COUNT_BITS = Integer.SIZE - 3; @Native public static final int SIZE = 32; SIZE 是Integer类中的常量),用29位二进制数表示线程池中当前活动的线程数量。所以,理论上,线程池中最少有0个线程,最多有536870911(29个1表示的二进制位)个线程。
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
变量CAPACITY 就代表workerCount的上限。1左移29位,也就是00000000 00000000 00000000 00000001 --> 00100000 00000000 00000000 00000000,再减去1的话,就是 00011111 11111111 11111111 11111111,前三位代表线程池运行状态runState,所以这里workerCount的理论最大值就应该是29个1,即536870911。
如何从AtomicInteger类中value属性获取线程池的运行状态和活动线程数呢?
在ThreadPoolExecutor类中提供了runStateOf和workerCountOf方法,runStateOf方法的作用是获取线程池的运行状态,workerCountOf方法的作用是从线程池中获取当前活动的线程数量。在下面分析线程池实现原理时,在execute方法中会调用这两个方法。
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
由上面分析可知,CAPACITY的高三位是0,低29位全是1,所以,(c&~CAPACITY)的结果就是c的高3位,(c&CAPACITY)的结果就是c的低29位。另外,由于AtomicInteger类中value属性的高三位表示线程池运行状态,低29位表示线程池中当前活动的线程数量,因此,将AtomicInteger类中的value属性作为参数,传递给runStateOf方法就可以得到线程池的运行状态,传递给workerCountOf方法就可以得到线程池中当前活动的线程数量。
线程池有以下五种运行状态:
(1)RUNNING:接受新任务,并处理队列任务。
private static final int RUNNING = -1 << COUNT_BITS;
-1在Java底层是由32个1表示的,左移29位的话,即111 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为1的话,表示RUNNING状态,即-536870912。
(2)SHUTDOWN:不接受新任务,但会处理队列任务。
private static final int SHUTDOWN = 0 << COUNT_BITS;
0在Java底层是由32个0表示的,无论左移多少位,还是32个0,即000 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位全部为0的话,表示SHUTDOWN状态,即0。
(3)STOP:不接受新任务,不会处理队列任务,而且会中断正在处理过程中的任务。
private static final int STOP = 1 << COUNT_BITS;
1在Java底层是由前面的31个0和1个1组成的,左移29位的话,即001 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为001的话,表示STOP状态,即536870912。
(4)TIDYING:所有的任务已结束,活动的线程数workerCount为0,线程过渡到TIDYING状态,将会执行terminated()钩子方法。
private static final int TIDYING = 2 << COUNT_BITS;
2在Java底层是由前面的30个0和后两位的10组成的,左移29位的话,即010 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为010的话,表示TIDYING状态,即1073741824。
(5)TERMINATED:terminated()方法已经完成。
private static final int TERMINATED = 3 << COUNT_BITS;
3在Java底层是由前面的30个0和后两位的11组成的,左移29位的话,即011 00000 00000000 00000000 00000000,也就是低29位全部为0,高3位为011的话,表示TERMINATED状态,即1610612736。
由上可知,运行状态的值按照RUNNING->SHUTDOWN->STOP->TIDYING->
TERMINATED顺序值是递增的,这些值之间的数值顺序很重要。随着时间的推移,运行状态单调增加,但是不需要经过每个状态。可能存在的线程池状态的转换如下所示:
(1)RUNNING -> SHUTDOWN:调用shutdown()方法后,或者线程池实现了finalize方法,在里面调用了shutdown方法,即隐式调用;
(2)(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()方法后;
(3)SHUTDOWN -> TIDYING:线程池和任务队列均为空时;
(4)STOP -> TIDYING:线程池为空时;
(5)TIDYING -> TERMINATED:terminated()钩子方法完成时。
当创建线程池后,初始时,线程池处于RUNNING状态;
如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TIDYING状态。
当terminated()钩子方法完成时,线程池由TIDYING转换到TERMINATED状态。
构造器中的参数:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。当线程池中活跃线程的数量小于corePoolSize时,会直接启动一个新的线程来处理任务,而不管线程池中是否有空闲线程。
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程。由上面corePoolSize属性可知,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中,如果缓存队列满了之后,再有任务到来时,就会再创建线程,直到线程数量达到maximumPoolSize的值。如果此时还有任务到来,就会使用拒绝处理任务的策略RejectedExecutionHandler,可能会抛出任务拒绝异常。
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0。
unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和SynchronousQueue。线程池的排队策略与BlockingQueue有关。
threadFactory:线程工厂,主要用来创建线程。
handler:表示拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务,即创建线程池调用execute或submit方法提交任务的线程处理任务,一般是主线程。
2、深入剖析线程池实现原理
ThreadPoolExecutor继承了AbstractExecutorService类,AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法,ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutdown等。Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable command),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的。
public class ThreadPoolExecutor extends AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService
public interface ExecutorService extends Executor
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
抽象类AbstractExecutorService中主要是submit方法,用于提交任务
//抽象类AbstractExecutorService中主要是submit方法,用于提交任务
public abstract class AbstractExecutorService implements ExecutorService {
//省略了其他方法
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
}
ThreadPoolExecutor类中的execute方法用于提交任务
public class ThreadPoolExecutor extends AbstractExecutorService {
//省略其他方法和属性
public void execute(Runnable command) {
//若提交的任务command为null,则抛出空指针异常
if (command == null)
throw new NullPointerException();
//workerCountOf(c)会获取线程池中当前正在活动的线程worker数量
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
//如果活动线程数小于corePoolSize,就创建一个核心线程worker执行该任务
if (addWorker(command, true))
return;
c = ctl.get(); //在addWorker方法中修改了线程池的状态和线程数量
}
//isRunning(c)是判断线程池是否处于运行状态,若处于运行状态,则将任务加到队列中
if (isRunning(c) && workQueue.offer(command)) {
//如果添加到队列成功了,会再检查一次线程池的状态。若线程池处于关闭状态,就将刚才添加的任务从队列中移除,并执行拒绝策略。
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command); //执行拒绝策略
//如果线程池的状态为SHUTDOWN,则不接受新任务,但会处理队列中的任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果上面通过offer方法将任务加入队列失败,就创建普通线程worker来执行任务
else if (!addWorker(command, false))
reject(command); //如果创建Worker线程失败,就执行拒绝策略
}
}
在ThreadPoolExecutor类中,最核心的提交任务的方法是execute()方法,虽然通过submit也可以提交任务,但是实际上submit方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可。
1、判断提交的任务command是否为null,若是null,则抛出空指针异常。
2、AtomicInteger类中有int类型的value属性,该值的高三位表示线程池运行状态,低29位表示线程池中当前活动的线程数量。通过AtomicInteger的get方法可以获取到value的值,即此处的int c = ctl.get()。AtomicInteger类中get方法:public final int get() { return value; }
3、调用workerCountOf(c)方法,得到线程池中当前活动的线程数量。
private static int workerCountOf(int c) { return c & CAPACITY; }
由上面分析可知,CAPACITY的高三位是0,低29位全是1,所以,(c&CAPACITY)的结果就是c的低29位。因此,将AtomicInteger类中的value属性作为参数传递给c的话,就可以得到线程池中当前活动的线程数量。
4、如果当前活动的线程数量小于核心池大小corePoolSize的值,就调用addWorker方法,该方法的作用是判断线程是否创建并启动成功,若成功,则返回true,否则返回false。如下面的源码一所示。由于Worker实现了Runnable接口,若新建的Worker线程被启动成功,则在该线程中执行提交的任务,并从任务队列中循环获取任务进行执行(如果任务队列中有任务的话)。如下面的源码二所示。所以,在addWorker方法中,创建线程,并通过该线程执行任务。
5、isRunning方法是判断线程池是否在运行状态,如果在运行状态,则通过队列的offer方法将任务添加到任务队列中。如果添加到队列成功了,会再检查一次线程池的状态。若线程池处于关闭状态,就将刚才添加的任务从队列中移除。
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
6、通过if (workerCountOf(recheck)==0)判断线程池的状态是否为SHUTDOWN,如果线程池的状态为SHUTDOWN,则不接受新任务,但会处理队列中的任务。
7、如果上面通过队列的offer方法将任务加入队列失败,就尝试直接创建Worker线程来执行任务。如果创建Worker线程失败,就执行拒绝策略。
2.1、源码一:addWorker方法
该方法的作用是:若创建并开启线程成功,则返回true,否则返回false。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) { //使用自旋+cas失败重试来保证线程竞争问题
int c = ctl.get();
int rs = runStateOf(c); //获取线程池的状态
// 如果线程池是关闭的,或者workQueue队列非空,就直接返回false,不做任何处理
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c); //获取线程池中当前活动的线程数量
//若线程数量已达到上限,或达到corePoolSize或maximumPoolSize,就拒绝创建线程
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//若线程数量未超过上限,就尝试修改ctl的value值,使其加1。修改了线程数量,
//也有可能修改了线程池的运行状态。这里用了cas操作,如果失败了,下一个循环会继续重试,
//直到设置成功。如果设置成功了,就跳出外层for循环,执行下面创建线程的代码
if (compareAndIncrementWorkerCount(c))
break retry;
//重读一次ctl,判断如果线程池的状态改变了,会再重新执行外循环
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建一个线程worker,将提交上来的任务firstTask交给worker。下面若开启该线程成功,
//就会执行该线程的run方法,并进一步调用ThreadPoolExecutor类的runWorker方法
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
//获取并判断线程池的状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//如果worker线程已经启动了,会抛出异常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//添加新建的worker到线程池中
workers.add(w);
int s = workers.size();
//更新历史线程数量的最大值
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true; //设置新增标志位
}
} finally {
mainLock.unlock();
}
//如果worker是新增的,就启动该线程,并设置启动成功标志位
if (workerAdded) {
//注意:启动线程是调用start方法,下面执行任务是调用run方法
t.start();
workerStarted = true;
}
}
} finally {
//如果启动失败,就从线程池中将该线程移除,并将线程数量减1
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
在上面的addWorker方法中调用了compareAndIncrementWorkerCount方法:
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
在上面的addWorker方法中调用了addWorkerFailed方法:该方法的作用是从线程池中移除线程,并将线程数量减1。
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
在上面的addWorkerFailed方法中调用了decrementWorkerCount方法:
private void decrementWorkerCount() {
do {} while (! compareAndDecrementWorkerCount(ctl.get()));
}
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
2.2、源码二:Worker类
Worker是ThreadPoolExecutor内部定义的一个内部类。它实现了Runnable接口,所以可以拿来当线程用。同时它还继承了AbstractQueuedSynchronizer同步器类,主要用来实现一个不可重入的锁。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
private static final long serialVersionUID = 6138294804551838833L;
//运行的线程,前面addWorker方法中就是直接通过启动这个线程来启动这个worker
final Thread thread;
/** Initial task to run. Possibly null. */
//当一个worker刚创建的时候,就先尝试执行这个任务
Runnable firstTask;
//记录完成任务的数量
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//通过Worker对象创建一个thread,当thread启动的时候,就是执行worker的run方法
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
//调用ThreadPoolExecutor的runWorker方法
runWorker(this);
}
//省略了其他方法
}
在Worker类的run方法中调用了ThreadPoolExecutor类的runWorker方法,该方法的作用就是执行任务。如果任务队列中有任务,就获取任务并执行。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//执行unlock方法,允许其他线程来中断自己
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//如果前面的firstTask有值,就直接执行这个任务,如果没有具体的任务,
//就执行getTask()方法从队列中获取任务,这里会不断执行循环体,
//除非线程中断或者getTask()返回null才会跳出这个循环
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
//判断线程池状态,如果线程池被强制关闭了,就终止当前线程
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//执行任务前调用。预留的方法,可扩展
beforeExecute(wt, task);
Throwable thrown = null;
try {
//注意:执行任务只需调用run方法,不是调用start方法
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++; //记录完成的任务数量
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
在上面的runWorker方法中调用了getTask方法,该方法的作用就是从任务队列中获取任务,如果获取成功,则返回队列中的第一个任务,否则返回null。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//如果线程池已经关闭了,就直接返回null,如果这里返回null,
//调用getTask方法处的代码就会跳出while循环,然后执行销毁线程的方法。
//SHUTDOWN状态表示执行了shutdown()方法,STOP表示执行了shutdownNow()方法
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//获取线程池中当前活动的线程数量
int wc = workerCountOf(c);
//如果设置了核心worker也会超时或者当前活动的worker数量超过了corePoolSize,
//就要根据时间判断是否要销毁线程了,其实就是从队列获取任务的时候要不要设置超时时间,
//如果超过这个时间,队列还没有任务进来,就会返回null
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果上一次循环从任务队列获取到的为null,这时候timedOut就会为true了
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//通过CAS来设置WorkerCount,如果多个线程竞争,只有一个可以设置成功。
//最后如果没设置成功,就进入下一次循环,说不定下一次worker的数量
//就没有超过corePoolSize了,也就不用销毁worker了
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//如果为核心线程设置了存活时间,就通过poll方法在指定时间内从任务队列中获取任务,
//若在指定时间内获取到了任务(即队列的头元素),则将任务赋值给r变量,没有获取到任务,
//则返回null给r变量。若没有设置超时时间,就通过take方法从任务队列中获取任务,
//若任务队列中暂时没有任务,就一直等待有任务。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true; //如果任务r为null,就设置timedOut为true
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
在上面的runWorker方法中调用了processWorkerExit方法,该方法的作用是:清除和记录垂死的工作线程。如果由于用户任务异常,或活动线程数量少于corePoolSize,或队列不为空但没有活动的线程,该方法就会从线程池中移除线程,并且可能终止线程池或替换工作线程。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
tryTerminate();
int c = ctl.get();
if (runStateLessThan(c, STOP)) {
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
addWorker(null, false);
}
}
2.3、任务提交后的流程图分析
用户通过execute或submit提交一个任务时,线程池会执行如下流程:
1. 判断线程池中当前运行的worker数量是否超过corePoolSize,如果不超过corePoolSize,就创建一个worker直接执行该任务。—— 线程池最开始是没有worker在运行的。
2. 如果正在运行的worker数量超过或者等于corePoolSize,那么就将该任务加入到workQueue队列中去。
3. 如果workQueue队列满了,也就是offer方法返回false的话,就检查当前运行的worker数量是否小于maximumPoolSize,如果小于就创建一个worker直接执行该任务。
4. 如果当前运行的worker数量大于等于maximumPoolSize,就执行RejectedExecutionHandler策略处理这个任务的提交。
3、使用示例
public class Test {
public static void main(String[] args) {
//创建一个线程池,corePoolSize为5,maximumPoolSize为10,
//keepAliveTime为200ms,任务队列长度为5。或者核心线程数等于CPU核数
// int corePoolSize = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize() +
",队列中等待执行的任务数目:"+ executor.getQueue().size() +
",已执行完的任务数目:" + executor.getCompletedTaskCount());
}
//关闭线程池
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
执行结果:
正在执行task 0
线程池中线程数目:1,队列中等待执行的任务数目:0,已执行完的任务数目:0
线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完的任务数目:0
正在执行task 1
线程池中线程数目:3,队列中等待执行的任务数目:0,已执行完的任务数目:0
正在执行task 2
线程池中线程数目:4,队列中等待执行的任务数目:0,已执行完的任务数目:0
正在执行task 3
线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完的任务数目:0
正在执行task 4
线程池中线程数目:5,队列中等待执行的任务数目:1,已执行完的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:2,已执行完的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:3,已执行完的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:4,已执行完的任务数目:0
线程池中线程数目:5,队列中等待执行的任务数目:5,已执行完的任务数目:0
线程池中线程数目:6,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 10
线程池中线程数目:7,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 11
线程池中线程数目:8,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 12
线程池中线程数目:9,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 13
线程池中线程数目:10,队列中等待执行的任务数目:5,已执行完的任务数目:0
正在执行task 14
task 3执行完毕
task 0执行完毕
task 2执行完毕
task 1执行完毕
正在执行task 8
正在执行task 7
正在执行task 6
正在执行task 5
task 4执行完毕
task 10执行完毕
task 11执行完毕
task 13执行完毕
task 12执行完毕
正在执行task 9
task 14执行完毕
task 8执行完毕
task 5执行完毕
task 7执行完毕
task 6执行完毕
task 9执行完毕
从执行结果可以看出,当线程池中线程的数目大于5时(即大于corePoolSize),便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。由于此例中maximumPoolSize的值是10(最多可以创建10个线程来执行任务),任务队列长度是5,任务数是15,任务队列中可以存储5个,所以刚好创建10个线程来执行剩下的10个任务。如果上面程序中,将for循环中改成执行20个任务,就会抛出任务拒绝异常了。
上面是通过ThreadPoolExecutor直接创建线程池的,另外,也可以使用Executors类中提供的几个静态方法来创建线程池:
Executors.newCachedThreadPool(); //创建容量大小为Integer.MAX_VALUE的线程池
Executors.newSingleThreadExecutor(); //创建容量为1的线程池
Executors.newFixedThreadPool(int); //创建固定容量大小的线程池
下面是这三个静态方法的具体实现:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从它们的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。
为什么阿里巴巴要禁用 Executors 创建线程池?
1、Executors.newSingleThreadExecutor()方法创建的是单线程线程池,只有一个核心线程,线程池最大数量也是1,也就是说线程池只能创建一个线程,那就是核心线程。使用的阻塞队列是LinkedBlockingQueue,其长度为Integer.MAX_VALUE,可以认为是无界队列,因此往队列中可以插入无限多的任务,在资源有限的时候容易引起OOM异常。另外因为无界队列,maximumPoolSize和keepAliveTime参数将无效,压根就不会创建非核心线程。
2、Executors.newFixedThreadPool(int nThreads)方法创建的是固定核心线程的线程池,固定核心线程数由用户传入。核心线程数和最大线程数都是nThreads参数。使用的阻塞队列也是LinkedBlockingQueue,可以往队列中插入无限多的任务,在资源有限的时候容易引起OOM异常。
3、Executors.newCachedThreadPool()方法可以根据需要创建任意数量的线程池。核心线程数为0,即没有核心线程。maximumPoolSize的值是Integer.MAX_VALUE,可以认为可以无限创建线程。使用的阻塞队列是SynchronousQueue,是一个不存储元素的队列,可以理解为队列永远是满的,因此最终会创建非核心线程来执行任务。对于非核心线程空闲60s时将被回收。因为Integer.MAX_VALUE非常大,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常。
4、Executors.newScheduledThreadPool :核心线程固定,大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
创建一个周期性执行任务的线程池。如果闲置,非核心线程会在DEFAULT_KEEPALIVEMILLIS时间内被回收。
public static ScheduledExecutorService newScheduledThreadPool (int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor( int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
ScheduledThreadPool可以创建的最大线程数是Integer.MAX_VALUE,可以认为是可以无限创建线程的,在资源有限的情况下容易引起OOM异常。
因此,最好是根据需要自己调用ThreadPoolExecutor构造函数传参创建合适的线程池。
在ThreadPoolExecutor中使用ThreadFactory创建线程:
package com.db.conn.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class DsConnThreadUtil {
private static final Logger log = LoggerFactory.getLogger(DsConnThreadUtil.class);
private static final int CORE_POOL_SIZE = 10;
private static final int MAX_POOL_SIZE = 100;
private static final int KEEP_ALIVE_TIME = 1;
private static final ThreadPoolExecutor THREAD_EXECUTOR;
static {
//通过ThreadFactory创建线程,给每个线程设置名称
ThreadFactory threadFactory = new ThreadFactory() {
AtomicInteger atomicInteger = new AtomicInteger();
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"Thread_" + atomicInteger.getAndIncrement());
}
};
ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(MAX_POOL_SIZE);
THREAD_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,
KEEP_ALIVE_TIME,TimeUnit.SECONDS,blockingQueue,threadFactory);
}
public static void saveOrUpdate(String dsId, String sql, Object... params){
Runnable t = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
DsConnUtil.saveOrUpdate(dsId,sql,params);
log.info("子线程新增或修改数据结束,当前线程名称====="
+ Thread.currentThread().getName());
}
};
THREAD_EXECUTOR.execute(t);
System.out.println("线程池中线程数目:" + THREAD_EXECUTOR.getPoolSize() +
",队列中等待执行的任务数目:" + THREAD_EXECUTOR.getQueue().size() +
",已执行完的任务数目:" + THREAD_EXECUTOR.getCompletedTaskCount());
log.info("新增或修改完成,主线程名称=====" + Thread.currentThread().getName());
}
public static void batchSaveOrUpdate(String dsId, String sql, List<Object[]> params){
Runnable t = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
DsConnUtil.batchSaveOrUpdate(dsId,sql,params);
log.info("子线程批量新增数据结束,当前线程名称=====" + Thread.currentThread().getName());
}
};
THREAD_EXECUTOR.execute(t);
System.out.println("线程池中线程数目:" + THREAD_EXECUTOR.getPoolSize() +
",队列中等待执行的任务数目:" + THREAD_EXECUTOR.getQueue().size() +
",已执行完的任务数目:" + THREAD_EXECUTOR.getCompletedTaskCount());
log.info("批量新增或修改完成,主线程名称=====" + Thread.currentThread().getName());
}
public static List<Map<String,Object>> queryForList(String dsId, String sql, Object... params){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程池中线程数目:" + THREAD_EXECUTOR.getPoolSize() +
",队列中等待执行的任务数目:" + THREAD_EXECUTOR.getQueue().size() +
",已执行完的任务数目:" + THREAD_EXECUTOR.getCompletedTaskCount());
return DsConnUtil.queryForList(dsId,sql,params);
}
}
4、如何合理配置线程池的大小
一般需要根据任务的类型来配置线程池大小:
如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1。
如果是IO密集型任务,参考值可以设置为2*NCPU。(NCPU表示CPU核数)
当然,这只是一个参考值,具体的设置还需要根据实际情况进行调整,比如可以先将线程池大小设置为参考值,再观察任务运行情况和系统负载、资源利用率来进行适当调整。
什么是CPU密集型、IO密集型?
CPU密集型(CPU-bound):
CPU密集型也叫计算密集型,指的是系统的硬盘、内存性能与CPU相比,不会差得很多,CPU在很短的时间内就可以完成从内存或硬盘中读写数据,即I/O在很短的时间就可以完成,而绝大部分时间都花在CPU处理运算的过程中,CPU Loading很高。
在多重程序系统中,大部分时间用来做计算、逻辑判断等CPU动作的程序称之CPU bound。例如一个计算圆周率至小数点一千位以下的程序,在执行的过程当中绝大部份时间用在三角函数和开根号的计算,便是属于CPU bound的程序。
CPU bound的程序一般而言CPU占用率相当高。这可能是因为任务本身不太需要访问I/O设备,也可能是因为程序是多线程实现,因此屏蔽掉了等待I/O的时间。
IO密集型(I/O bound):
IO密集型指的是系统的硬盘、内存性能与CPU相比,要差很多,此时,系统运作,大部分时间花在CPU等待从内存或硬盘中读写数据,即I/O操作需要花费很长时间,此时CPU Loading并不高。
I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。
CPU密集型 vs IO密集型:
我们可以把任务分为计算密集型和IO密集型。
计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,CPU密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。
第二种任务的类型是IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。
总之,计算密集型程序适合C语言多线程,I/O密集型适合脚本语言开发的多线程。