线程池
线程池顾名思义,就是一个放置线程的池子。就跟数据库连接池差不多。线程池通过对并发线程的控制,能有效的节省系统资源的浪费,提高系统的性能。
学习线程池,先了解一下线程池的一个基本结构:
Executor
public interface Executor {
void execute(Runnable command);
}
Executor是一个接口,其中只有一个方法,就是execute方法。所以Executor实际就是一个线程的执行者。
这里就不把子类的所有方法全部列出来,全部学习一遍,大体可以参照API进行学习。这里主要学习线程池ThreadPoolExecutor方法开始学习,来了解学习线程池的一个运行原理。
ThreadPoolExecutor的一些重要属性
//ctl用来表示线程池的运行状态,和线程池中任务的数量。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//进制位。最大整数转换成二进制的位数
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池的最大容量2的29次方减1
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;
//当所有的任务已终止,ctl记录的"任务数量"为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
private static final int TIDYING = 2 << COUNT_BITS;
//线程池彻底终止,就变成TERMINATED状态。
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; }
//初始化ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
//线程的阻塞队列
private final BlockingQueue<Runnable> workQueue;
//互斥锁
private final ReentrantLock mainLock = new ReentrantLock();
//线程工作集
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;
这么多变量,可能要记住也不是很容易。通过分析Execute方法可以更加好的理解工作原理和这些属性的意义
public void execute(Runnable command) {
//执行的线程为空抛出空指针异常。
if (command == null)
throw new NullPointerException();
//这里整体可以分三步。
//1. 如果当前线程池中任务数量(一个任务可以理解就是一个线程)小于中心池的容量(corePoolSize),就直接为command新建一个线程,加入任务集workers,并启动该线程。
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//2.如果当前任务数量大于了corePoolSize,并且线程池是可运行状态。就把任务加入到任务队列workQueue中。 加入队列之后,再次确认线程池的状态,这个时候状态不是可运行的,那就把任务从队列中删除,并尝试终止线程池。如果是可运行,那么就检查线程池中工作数量是否为0,如果没有了,那么就添加一个任务为空的线程。
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);
}
//3.如果任务数量大于中心池数量,添加对了也失败了(这里队列是BlockingQueue,前面学习过队列有有界队列和无界队列,所以有可能队列满了导致添加失败。活着其它原因),那么就再进行一次尝试添加到任务集中去,如果失败,执行拒绝策略。
else if (!addWorker(command, false))
reject(command);
}
通过上面的步骤学习,可以大致的理一下思路,线程池,有一个中心池容量,这个容量没有满,就可以直接添加任务运行,而任务是被 添加到一个HashSet的Worker中。如果满了,就把任务添加到一个BlockingQueue队列中。都失败了,就直接运行一个拒绝策略。所以,就要理解三个东西:
- 工作集。
- 任务队列
- 拒绝策略。
理解了这三个东西,那么大致就可以了解线程池的一个基本原理。
工作集 Worker
//Worker是线程池的一个内部类 集成了AQS,实现了Runnable接口。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread;
Runnable firstTask;
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker 运行前,不准中断
this.firstTask = firstTask; //初始任务值
this.thread = getThreadFactory().newThread(this);//通过线程工为当前任务创建一个线程
}
public void run() {
runWorker(this);//运行
}
protected boolean isHeldExclusively() {
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()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
工作集的就是把任务通过线程工厂创建一个该任务的线程并运行。
* 任务队列*
从定义上看我们知道任务队列是一个BlockingQueue。所以线程池中的任务队列可以是任意BlockingQueue的子类。但是常用线程池中常用的的是ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue .下一节会学习重用的线程池类型。
拒绝策略
AbortPolicy – 当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。
CallerRunsPolicy – 当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。
DiscardOldestPolicy – 当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。
DiscardPolicy – 当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。
大致了解学习了线程池的一个主要运行过程和基本原理。下一节将会学习JDK自带的几种线程池,更加进一步学习和理解线程池。