本章主要记录近期学习线程池的一些知识心得体会,记录一个通读源码的过程。
一、线程池的目的:
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁。如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因。
JVM会为每一个线程分配相应的Heap空间,虚拟机中用-Xss:设置每个线程的堆栈大小(也就是说,在相同物理内存下,减小这个值能生成更多的线程)。当线程数量超过空间的限制时会抛出OutOfMemory异常(OOM)。
二、线程池的组成部分:
1、线程池管理器(ThreadPoolManager):用于创建并管理线程池;
2、工作线程(WorkThread):线程池中的线程;
3、任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行;
4、任务队列(Queue):用于存放没有处理的任务,提供一种缓存机制。
三、线程池实现过程分析:
示例一类常用的线程池:ThreadPoolExecutor(Spring可以通过ThreadPoolExecutorFactoryBean加载ThreadPoolExecutor,参数可通过Bean注入)
ThreadPoolExecutor主要参数包括:corePoolSize、 maximumPoolSize、keepAliveTime、Queue、ThreadFactory、RejectedExecutionHandler逐一对分析相关参数。
1、corePoolSize:线程池将new出的核心线程数。核心线程在处理完firstTask的之后,被线程池不会回收其资源,而是处于阻塞状态,等待从任务队列(Queue)中获取下一个Task如此循环执行(但如果设置有allowCoreThreadTimeOut=true,则如果核心线程在等待keepAliveTime时间内没有从队列中获取到新任务,也会被线程池回收)。相关源码如下:
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 {
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();
}
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
boolean timed; // Are workers subject to culling?
for (;;) {
int wc = workerCountOf(c);
timed = allowCoreThreadTimeOut || wc > corePoolSize;
if (wc <= maximumPoolSize && ! (timedOut && timed))
break;
if (compareAndDecrementWorkerCount(c))
return null;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
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);
}
3、keepAliveTime:辅助线程在执行完上一个任务之后保持活性等待下一个Task的时间,一般带有TimeUnit单位戳。
4、BlockingQueue:存放任务的阻塞队列,一般没有特殊要求只用两种,LinkedBlockingQueue 链表阻塞队列与SynchronousQueue 同步阻塞队列。当期望的队列深度为0时,则使用后者,否则为前者指定一个队列大小(默认为Integer.MAX_VALUE作为queueCapacity)作为存储队列,除coreThread的firstTask、任务加入队列失败这两种情况,其他的线程均从队列中获取任务(先进先出),新添加的任务如果不能被立马执行,也会被加入到队列中。其中对同步阻塞队列的解释如下:
/**
* A {@linkplain BlockingQueue blocking queue} in which each insert
* operation must wait for a corresponding remove operation by another
* thread, and vice versa. A synchronous queue does not have any
* internal capacity, not even a capacity of one. You cannot
* <tt>peek</tt> at a synchronous queue because an element is only
* present when you try to remove it; you cannot insert an element
* (using any method) unless another thread is trying to remove it;
* you cannot iterate as there is nothing to iterate. The
* <em>head</em> of the queue is the element that the first queued
* inserting thread is trying to add to the queue; if there is no such
* queued thread then no element is available for removal and
* <tt>poll()</tt> will return <tt>null</tt>. For purposes of other
* <tt>Collection</tt> methods (for example <tt>contains</tt>), a
* <tt>SynchronousQueue</tt> acts as an empty collection. This queue
* does not permit <tt>null</tt> elements.
*/
5、
ThreadFactory:线程工厂,所有的线程都从线程工厂中产出,由线程工厂同一分配创建,并统一分组、命名规范等。默认的线程工厂代码如下:
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
6、RejectedExecutionHandler:当任务的请求由于线程池的限制原因无法添加线程处理或进入队列时,执行的拒绝处理操作。线程池默认的处理操作源码:
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always.
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
四、线程池的底层实现原理(对状态、线程池大小的控制)
1、线程池的大小及状态用一个AtomicInteger变量进行控制:
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; }
用移位运算及按位与、按位或对线程数、线程池状态等进行控制
2、线程池包括5种状态:Running、ShutDown、Stop、Tidying、Terminated
RUNNING: Accept new tasks and process queued tasks
SHUTDOWN: Don't accept new tasks, but process queued tasks
STOP: Don't accept new tasks, don't process queued tasks,and interrupt in-progress tasks
TIDYING: All tasks have terminated, workerCount is zero,the thread transitioning to state TIDYING will run the terminated() hook method
TERMINATED: terminated() has completed
3、线程池锁 ReentrantLock mainLock = new ReentrantLock();
五、个人总结
在线程池的学习过程中,在了解源码的同时,也学到了相当多的优化编程小技巧,如:
if (isRunning(c) && workQueue.offer(command))
在判断中执行逻辑。通读源码的收益还是很可观的。