一.为什么要使用线程池?
两个方面:1. 减少开销 2. 方便管理
Java作为高级语言建立在操作系统之上,通过Java程序创建出的用户线程最终其实就是映射到操作系统内核上的内核线程,即每次创建回收线程都需要通过内核调用,开销较大,通过线程池就不必频繁创建回收线程,而是将已创建的线程重复利用起来,也可以防止开发人员无意识的频繁创建线程浪费资源。
其次线程池可以维护线程ID、线程状态、任务执行状态等信息,便于管理。
二.线程池七大参数
- corePoolSize 核心线程数,默认情况下一直存活的线程且在任务量较少时由这些线程主要处理任务
- int maximumPoolSize 最大线程数,任务数量过多时临时帮助去帮助处理任务的线程(减去核心线程数),如果没有任务运行,在超过最大存活时间后会被回收
- long keepAliveTime 最大存活时间
- TimeUnit unit 超时单位
- BlockingQueue workQueue 阻塞队列
- ThreadFactory threadFactory 线程工厂:创建线程的,一般不用动
- RejectedExecutionHandler handler 拒绝策略
三.四种拒绝策略
- AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
- CallerRunsPolicy:由调用线程处理该任务。
- DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
- DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
四.简单使用案例
public class ThreadPool {
private static ExecutorService pool;
public static void main( String[] args )
{
//maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常
pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
for(int i=0;i<3;i++) {
pool.execute(new ThreadTask());
}
}
}
public class ThreadTask implements Runnable{
public ThreadTask() {
}
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
五.源码分析
1.继承关系
ThreadPoolExecutor继承自抽象类AbstractExecutorService,AbstractExecutorService中实现了ExecutorService接口中的众多方法,比如ThreadPoolExecutor自己没有实现的submit()方法,完全复用自父类AbstractExecutorService中的该方法
2.静态变量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3; //COUNT_BITS常量32-3=29
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//线程池的五种状态
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;
由于计算机中的int由补码表示,-1的补码为32个1,对于running-1左移29为则其表示为高三位为1其余29位为0的序列,这样推算五种状态的序列码为:
running:111 000000…000 接受新任务,也能处理阻塞队列里的任务
shutdown:000 000000…000 不接受新任务,但处理阻塞队列里的任务
stop:001 000000…000 不接受新任务,不处理阻塞队列里的任务和中断处理过程中的任务
tidying:010 000000…000 当所有任务都执行完了,当前线程已经没有工作线程,将要调用terminated方法
terminated:011 000000…000 terminated方法调用完成
即高3位表示线程状态,其余低29位表示线程数
而第一行的原子类的ctl变量通过调用ctlof方法将线程池初始化状态标记位running,工作线程数为0
private static int ctlOf(int rs, int wc) { return rs | wc; }
状态转换图:
3.成员变量
//类型是BlockingQueue的interface,可以由用户自定义的阻塞队列其父类AbstractExecutorService会将Callable的对象也转换为Runnable的自类FutureTask
private final BlockingQueue<Runnable> workQueue;
//线程池中的一些操作需要线程间同步,需要用到锁
private final ReentrantLock mainLock = new ReentrantLock();
//对线程以及一些其他状态进行封装的HashSet,workers即用来存储所有工作线程的集合
private final HashSet<Worker> workers = new HashSet<Worker>();
private final Condition termination = mainLock.newCondition();
//线程池中活跃线程的最大数
private int largestPoolSize;
//线程池总共处理了多少个任务
private long completedTaskCount;
//类型为ThreadFactory接口的线程工厂,即用户可以自定义的线程工厂
private volatile ThreadFactory threadFactory;
//指定拒绝策略
private volatile RejectedExecutionHandler handler;
//线程空闲时最长的存活时间
private volatile long keepAliveTime;
//布尔类型,是否需要让核心线程始终保持存活
private volatile boolean allowCoreThreadTimeOut;
//核心线程数
private volatile int corePoolSize;
//最大线程数,当阻塞队列满了线程池可以创建新的线程来处理任务,直到达到最大线程数
private volatile int maximumPoolSize;
以上变量中,核心线程数,最大线程数,拒绝策略是三个重点
4.内部类Worker
Worker类是对线程的封装,包含了对线程的管理细节:
private final class Worker
extends AbstractQueuedSynchronizer //继承了AQS,实现了Runnable接口
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); //构造函数中将AQS的state初始化为-1,目的是为了在初始化期间不接受中断信号直到runWorker方法开始运行工作线程真正开始处理任务时再修改为0,相当于锁被释放的状态,当前工作线程可以接受当前线程的中断信号
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this);
}
protected boolean isHeldExclusively() { //下面的方法均为AQS相关的获取释放锁的方法
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() { //用来中断当前工作线程的方法
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { //对state值进行判断不为0则退出
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
Worker实现了Runnable代表其对象是一个异步任务,它的工作就是从阻塞队列里获取任务然后处理,继承AQS则代表Worker有锁的能力,比如Woker正在处理一个持有锁的任务,这时Woker接收到一个中断信号,那么就可以选择等任务完成释放锁后中断或者直接中断
其中最主要的runWorker方法:
worker启动时便会调用
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//while循环当前线程不断去阻塞队列里获取任务,每当获取到任务就先加锁再处理
while (task != null || (task = getTask()) != null) { //当fistTask不为null或从阻塞队列里获取的任务不为null就处理该任务
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task); //对每个任务进行切面,在ThreadPoolExecutor中为空实现,想要继承ThreadPoolExecutor来实现自己的线程池并支持切面操作得自己实现
Throwable thrown = null;
try {
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); //对每个任务进行切面,跟上面beforeExecute共同完成切面
}
} finally { //释放锁,完成任务数++
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly); //任务出现了null跳出while循环执行该方法来对worker进行回收
}
}
getTask方法:
getTask方法返回null则会跳出while循环触发回收操作,看如何会触发回收操作
private Runnable getTask() {
boolean timedOut = false; //布尔类型记录上一次从阻塞队列里poll任务是否超时
for (;;) { //自旋循环
int c = ctl.get();
int rs = runStateOf(c);//获取当前线程池状态
//如果线程池状态已经是stop,tidying,terminated或者线程池状态是shutdown且工作队列为空则返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c); //获取woker数量
//如果当前工作线程数超过了最大线程数或者达到了核心线程数的回收条件,且池中还有其他工作线程或阻塞队列为空时则开始尝试回收当前worker
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try { //不满足上述两种条件就从阻塞队列里获取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take(); //poll操作在队列为空时直接返回null,take则会等待直到队列中有任务可以被取出
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
则getTask方法总体会达到如下效果:
- 如果从阻塞队列里获取了任务,则返回该任务
- 如果阻塞队列里没任务,则阻塞等待阻塞队列里的任务
- 如果当前worker需要被回收则返回null
5.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)) { //在上层并发量较大时新增线程失败,则向阻塞队列offer一个任务
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //如果成功提交到了阻塞队列判断当前线程池状态如果处于非running则尝试移除任务
reject(command); //如果成功移除了任务则触发拒绝策略
else if (workerCountOf(recheck) == 0) //如果移除失败
addWorker(null, false); //则给该任务一个被执行的机会,尝试新增worker去消费阻塞队列里的任务
}
else if (!addWorker(command, false)) //如果第二个if阻塞队列已满则尝试创建线程,这里创建的也是非核心线程
reject(command); //如果已经到达最大线程数则触发拒绝策略
}
线程池提交任务流程图: