线程池简介
线程池的优点
重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销
能有效控制线程池的最大并发数量,避免大量线程之间因相互抢占系统资源而导致线程阻塞.
能够对线程进行简单的管理,并提供定时执行以及制定间隔循环执行等功能.
TheadPoolExecutor
ThreadPoolExecutor
是线程池真正实现,它的构造方法提供一些列参数来配置线程池,
public ThreadPoolExecutor executor =new ThreadPoolExecutor(10,100,60,TimeUnit.SECONDS,new LinkedBlockingQueue());
参数 | 说明 |
---|---|
corePoolSize | 核心线程数量,线程池维护线程的最少数量 |
maximumPoolSize | 线程池维护线程的最大数量 |
keepAliveTime | 线程池除核心线程外的其他线程的最长空闲时间,超过该时间的空闲线程会被销毁 |
unit | keepAliveTime的单位,TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS |
workQueue | 线程池所使用的任务缓冲队列 |
threadFactory | 线程工厂,用于创建线程,一般用默认的即可 |
handler | 线程池对拒绝任务的处理策略 |
当线程池任务处理不过来的时候(什么时候认为处理不过来后面描述),可以通过handler指定的策略进行处理,ThreadPoolExecutor
提供了四种策略:
ThreadPoolExecutor.AbortPolicy
:丢弃任务并抛出RejectedExecutionException
异常;也是默认的处理方式。ThreadPoolExecutor.DiscardPolicy
:丢弃任务,但是不抛出异常。ThreadPoolExecutor.DiscardOldestPolicy
:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)ThreadPoolExecutor.CallerRunsPolicy
:由调用线程处理该任务
还可以通过实现RejectedExecutionHandler
接口自定义处理方式。
线程池的分类
- FixedThreadPool
ExecutorService service = Executors.newFixedThreadPool(2); //线程数量
固定线程数量的线程池,当线程空闲时不会被回收,另外任务队列没有大小限制
- CachedThreadPool
ExecutorService service = Executors.newCachedThreadPool();
不固定线程数量的线程池,它只有非核心线程,线程最大值为intger.MAX_VALUE
, 当线程空闲60s会被回收,和FiexdThreadPool
不一样的是,CachedThreadPool
的任务队列相当于一个空集合,任何新任务都可以立即被执行,适用于执行大量的耗时较少的任务.整个线程池都处于闲置状态,
ScheduledThreadPool
ExecutorService service = Executors.newScheduledThreadPool(20);
它的核心线程数量固定,而非核心线程数量无限制,核心线程闲置会立即被回收,主要作用是执行定时任务和固定周期的重复任务,
SingleThreadPool
ExecutorService service = Executors.newSingleThreadExecutor();
它只有一个核心的线程,作用:确保所有的任务都在同一个线程中按顺序执行.他同意所有外界任务到一个线程中,实现同步.
newWorkStealingPool(1.8 jdk)
ExecutorService executorService = Executors.newWorkStealingPool();
java8新增的创建一个具有抢占式操作的线程池,这个线程池适合做一些耗时操作,它的实现是基于(work-stealing)算法来实现
看看源码怎么说
public class ThreadPoolExecutor extends AbstractExecutorService {
//用来标记线程池状态(高3位),线程个数(低29位) 默认是RUNNING状态,线程个数为0
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程个数掩码位数
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程最大个数(低29位)00011111111111111111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
/****************************线程池的状态*********************/
//(高3位):11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//(高3位):00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//(高3位):00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//(高3位):01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//(高3位):01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// 获取高三位 运行状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 获取低29位 线程个数
private static int workerCountOf(int c) { return c & CAPACITY; }
// 计算ctl新值,线程状态 与 线程个数
private static int ctlOf(int rs, int wc) { return rs | wc; }
}
任务提交的实现
1. Executor.execute()
void execute(Runnable command);
通过Executor.execute()
方法提交的任务,必须实现Runnable接口,该方式提交的任务不能获取返回值,因此无法判断任务是否执行成功。
public void execute(Runnable command) {
....
//获取当前线程池的状态+线程个数
int c = ctl.get();
//当前线程池线程个数是否小于corePoolSize,小于则开启新线程运行
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果线程池处于RUNNING状态,则添加任务到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
//二次检查
int recheck = ctl.get();
//如果当前线程池状态不是RUNNING则从队列删除任务,并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//否者如果当前线程池线程空,则添加一个线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果队列满了,则新增线程,新增失败则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
总结
execute
方法关键还得看addWorker()
如果当前线程池线程个数小于
corePoolSize
则开启新线程否则添加任务到任务队列
如果任务队列满了,则尝试新开启线程执行任务,如果线程个数大于
maximumPoolSize
则执行拒绝策略。
addWorker()
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//循环cas增加线程个数
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//cas增加线程个数,同时只有一个线程成功
if (compareAndIncrementWorkerCount(c))
break retry;
//cas失败了,则看线程池状态是否变化了,变化则跳到外层循环重试重新获取线程池状态,否者内层循环重新cas。
c = ctl.get();
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//到这里说明cas成功了
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//加独占锁,为了workers同步,因为可能多个线程调用了线程池的execute方法。
mainLock.lock();
try {
//重新检查线程池状态,为了避免在获取锁前调用了shutdown接口(3)
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 添加任务
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//添加成功则启动任务
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
内层循环作用是使用CAS增加线程个数,如果线程个数超限则返回false,否者进行CAS,CAS成功则退出双循环,否者CAS失败了,要看当前线程池的状态是否变化了,如果变了,则重新进入外层循环重新获取线程池状态,否者进入内层循环继续进行CAS尝试。
到了第二部分说明CAS成功了,也就是说线程个数加一了,但是现在任务还没开始执行,这里使用全局的独占锁来控制workers
里面添加任务,其实也可以使用并发安全的set,但是性能没有独占锁好(这个从注释中知道的)。这里需要注意的是要在获取锁后重新检查线程池的状态,这是因为其他线程可可能在本方法获取锁前改变了线程池的状态,比如调用了shutdown
方法。添加成功则启动任务执行。
工作线程Worker的执行
//Worker()默认的构造方法
Worker(Runnable firstTask) {
/*这里添加一个新状态-1是为了避免当前线程worker线程被中断,比如调用了线程池的shutdownNow,如果
当前worker状态>=0则会设置该线程的中断标志。这里设置了-1所以条件不满足就不会中断该线程了
。运行runWorker时候会调用unlock方法,该方法吧status变为了0,所以这时候调用shutdownNow会中断worker线程。*/
setState(-1);
this.firstTask = firstTask;
//创建一个线程
this.thread = getThreadFactory().newThread(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); //允许中断,status设置为0
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// 如果线程池当前状态至少是stop,则设置中断标志;
// 如果线程池当前状态是RUNNININ,则重置中断标志,重置后需要重新
//检查下线程池状态,因为当重置中断标志时候,可能调用了线程池的shutdown方法
//改变了线程池状态。
if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
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);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//执行清理工作
processWorkerExit(w, completedAbruptly);
}
}
runWorker
方法是线程池的核心:
1.线程启动之后,通过unlock
方法释放锁,设置AQS
的state为0,表示运行中断;
2.获取第一个任务firstTask
,执行任务的run
方法,不过在执行任务之前,会进行加锁操作,任务执行完会释放锁;
3.在执行任务的前后,可以根据业务场景自定义beforeExecute
和afterExecute
方法;
4.firstTask
执行完成之后,通过getTask
方法从阻塞队列中获取等待的任务,如果队列中没有任务,getTask
方法会被阻塞并挂起,不会占用cpu
资源
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果当前线程池状态>=STOP 或者线程池状态为shutdown并且工作队列为空则,减少工作线程个数
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//根据timed选择调用poll还是阻塞的take
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
整个
getTask()
操作在自旋下完成:
1.workQueue.take
:如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take
方法返回任务,并执行;
2.workQueue.poll
:如果在keepAliveTime
时间内,阻塞队列还是没有任务,则返回null;
所以,线程池中实现的线程可以一直执行由用户提交的任务。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly)
decrementWorkerCount();
//统计整个线程池完成的任务个数
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试设置线程池状态为TERMINATED,如果当前是shutdonw状态并且工作队列为空
//或者当前是stop状态当前线程池里面没有活动线程
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);
}
}
shutdown操作
- 调用shutdown后,线程池就不会在接受新的任务了,但是工作队列里面的任务还是要执行的,但是该方法立刻返回的,并不等待队列任务完成在返回。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//权限检查
checkShutdownAccess();
//设置当前线程池状态为SHUTDOWN,如果已经是SHUTDOWN则直接返回
advanceRunState(SHUTDOWN);
//设置中断标志
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//尝试状态变为TERMINATED
tryTerminate();
}
//如果当前状态>=targetState则直接返回,否者设置当前状态为targetState
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
//中断标志
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
//设置所有线程的中断标志,主要这里首先加了全局锁,同时只有一个线程可以调用shutdown时候设置中断标志,然后尝试获取worker自己的锁,获取成功则设置中断标示
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
shutdownNow
- 尝试停止正在运行的任务队列,并且返回等待的任务队列
//调用队列的drainTo一次当前队列的元素到taskList,可能失败,如果调用drainTo后队列海不为空,则循环删除,并添加到taskList
public List shutdownNow() {
List tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//设置线程池状态为stop
advanceRunState(STOP);
//移动队列任务到tasks
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
//调用队列的drainTo一次当前队列的元素到taskList,
//可能失败,如果调用drainTo后队列海不为空,则循环删除,并添加到taskList
private List drainQueue() {
BlockingQueue q = workQueue;
List taskList = new ArrayList();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
2. ExecutorService.submit()
通过ExecutorService.submit()
方法提交的任务,可以获取任务执行完的返回值。
Future submit(Callable task);
//实现
public Future> submit(Runnable task) {
if (task == null) throw new NullPointerException();
//新建带返回结果的线程
RunnableFuture ftask = newTaskFor(task, null);
//调用 Executor.execute()方法提交
execute(ftask);
return ftask;
}
Future和Callable实现
在实际业务场景中,Future
和Callable
基本是成对出现的,Callable
负责产生结果,Future
负责获取结果。
-
Callable
接口类似于Runnable
,只是Runnable
没有返回值。 -
Callable
任务除了返回正常结果之外,如果发生异常,该异常也会被返回,即Future
可以拿到异步执行任务各结 果; -
Future.get
方法会导致主线程阻塞,直到Callable
任务执行完成;
public class FutureTask implements RunnableFuture {
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
}
FutureTask
在不同阶段拥有不同的状态state,初始化为NEW状态;FutureTask
类实现了RunnableFuture
接口,这样就可以通过Executor.execute
方法提交FutureTask
到线程池中等待被执行,最终执行的是FutureTask
的run方法;
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
FutureTask.run
方法是在线程池中被执行的,而非主线程
通过执行
Callable
任务的call方法;如果call执行成功,则通过set方法保存结果;
如果call执行有异常,则通过
setException
保存异常;set()
和setException()
最终都会调用finishCompletion()
来通知主线程任务已经执行完成
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (U.compareAndSwapObject(this, WAITERS, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
//唤醒主线程,
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
执行
FutureTask
类的get方法时,会把主线程封装成WaitNode
节点并保存在waiters
链表中;FutureTask
任务执行完成后,通过UNSAFE
设置waiters的值,并通过LockSupport
类unpark
方法唤醒主线程;
关于线程池的总结
线程池巧妙的使用一个
Integer
类型原子变量来记录线程池状态和线程池线程个数,设计时候考虑到未来(2^29)-1个线程可能不够用,到时只需要把原子变量变为Long类型,然后掩码位数变下就可以了,但是为啥现在不一劳永逸的定义为Long那,主要是考虑到使用int类型操作时候速度上比Long类型快些。通过线程池状态来控制任务的执行,每个
worker
线程可以处理多个任务,线程池通过线程的复用减少了线程创建和销毁的开销,通过使用任务队列避免了线程的阻塞从而避免了线程调度和线程上下文切换的开销。另外需要注意的是调用
shutdown
方法作用仅仅是修改线程池状态让现在任务失败并中断当前线程,这个中断并不是让正在运行的线程终止,而是仅仅设置下线程的中断标志,如果线程内没有使用中断标志做一些事情,那么这个对线程没有影响。
感谢原文作者提供的优秀思路