线程池
帮我们重复管理线程,避免创建大量的线程增加开销
池化及线程池流程
-
先建立一个线程池,已经创建一定数量的空闲线程;
-
当子任务需要创建一个线程时,直接从线程池中获取空闲线程(对应选取空闲的门),执行任务;
-
任务完成后,将线程归还到线程池中,重归空闲;
-
其余任务亦是如此;
-
最后整个任务结束后,销毁所有线程,关闭线程池。
实际上过程
创建线程池之后,当需要创建一个线程,调用线程池execute提交一个runnable,然后判断工作线程数是否小于核心线程数,如果小于则调用addworker新建核心线程,否则判定线程池是否处于运行中状态,同时尝试用非阻塞方法向工作队列放入任务,这里还会二次检查线程池的一个运行状态(是否需要添加工作线程或执行当前方法时候线程池已经shutdown了),如果当前工作线程数量为0则创建要给非核心线程并且传入任务对象为null,否则尝试创建非核心线程传入工作实例执行,调用addworker(有个boolean型的参数,true是核心,false是非核心)新建非核心线程,否则调用reject方法调用拒绝策略
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);
}
任务在内部实际上是由封装的worker执行的,线程池中每一个具体的工作线程被封装成worker,它继承了aqs实现了runnable接口
他的run方法里调用了runworker方法
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 (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
在这个方法中先获取了当前线程,实际上就是worker持有的线程实例,然后调用unlock对worker进行解锁,因为构造worker的时候是吧state状态设置成了-1,这是因为worker实例刚创建的时候state默认值为0,此时线程尚未启动,不能再这个时候进行线程中断。然后继续runworker方法,它通过while循环调用getTask方法从任务队列中获取任务
(初始化任务对象不为null,或者从任务队列获取任务不为空,getTask()由于使用了阻塞队列,这个while循环如果命中后半段(gettask)会处于阻塞或者超时阻塞状态,getTask()返回为null会导致线程跳出死循环使线程终结),
如果为空的话直接嗲用processworkerexit处理线程退出,然后采用worker的lock进行加锁,然后进行判定,如果线程池正在停止,那么要确保当前工作线程是中断状态,否则要保证不是中断状态,然后就开始执行钩子方法和run任务和异常情况的钩子方法,最后清空任务的临时变量设为null(传入的worker中的初始化传入的任务对象一开始放在一个临时对象中,然后设为null了),累加worker完成的任务数,最后解锁(本质是AQS释放资源,设置state为0),执行完会调用处理线程退出的方法
while
循环跳出意味着runWorker()
方法结束和工作线程生命周期结束(Worker#run()
生命周期完结),会调用processWorkerExit()
处理工作线程退出的后续工作
addworker
调用addworker增加工作线程时候会有两个参数,第一个是任务,第二个是断定是否是核心线程
先判定线程池状态,,如果是至少shutdown并且至少stop或传入任务实例非null或者任务队列为空则返回false
然后采用重新获取工作线程数,核心线程则与核心线程数进行比较,非核心线程则与max比较,如果小于的话则正常向下执行通过cas更新工作线程数,,失败了则判定线程池是否更改了状态,如果线程池状态已经由RUNNING已经变为SHUTDOWN,则重新跳出到外层循环继续执行,传入任务实例创建worker实例,worker构造里面会通过线程工厂创建新的thread对象,然后加全局锁,主要在加锁的前提下判断ThreadFactory创建的线程是否存活或者判断获取锁成功之后线程池状态是否已经更变为SHUTDOWN
-
如果线程池状态依然为RUNNING,则只需要判断线程实例是否存活,需要添加到工作线程集合和启动新的Worker
-
如果线程池状态小于STOP,也就是RUNNING或者SHUTDOWN状态下,同时传入的任务实例firstTask为null,则需要添加到工作线程集合和启动新的Worker
换言之,如果线程池处于SHUTDOWN状态下,同时传入的任务实例firstTask不为null,则不会添加到工作线程集合和启动新的Worker,这一步其实有可能创建了新的Worker实例但是并不启动(临时对象,没有任何强引用),这种Worker有可能成功下一轮GC被收集的垃圾对象
添加道工作线程集合中后尝试更新历史峰值工作线程数,然后进行标记代表更新工作线程成功,后面才会调用start方法启动真实的线程实例,最后进行一个解锁
如果成功添加了工作线程,则调用worker内部的线程实例t的Threadstart方法启动真实的线程实例,如果线程启动失败则从工作线程集合移除对应的worker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && 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;
}
getTask
前面也是判定线程池状态,然后比较工作线程总数,看是否设置了核心线程过期,(allowCoreThreadTimeOut || wc > corePoolSize;),如果timed为true,通过poll()方法做超时拉取,keepAliveTime时间内没有等待到有效的任务,则返回null,如果timed为false,通过take()做阻塞拉取,会阻塞到有下一个有效的任务时候再返回(一般不会是null)
processworkerexit 清理worker,执行完工作线程即终结
如果因为抛出用户异常导致线程中界直接将工作线程数减一接口
否则加全局锁并且更新全局已完成任务记录数,然后从工作线程集合中移除将要终结的worker,解锁
调用更改线程池状态方法tryTerminate
判断线程池的状态,如果是下面三种情况下的任意一种则直接返回:
1.线程池处于RUNNING状态
2.线程池至少为 TIDYING状态,也就是TIDYING或者TERMINATED状态,意味着已经走到了下面的步骤,线程池即将终结
3.线程池至少为STOP状态并且任务队列不为空
工作线程数部位0,则中断工作线程集合中的第一个空闲的工作线程
加全局锁,cas更改线程池状态为清理状态TIDYING,最后更新线程池状态为终结状态,唤醒阻塞在termination状态的所有线程
如果线程池的状态小于STOP,也就是处于RUNNING或者SHUTDOWN状态的前提下:
1.如果线程不是由于抛出用户异常终结,如果允许核心线程超时,则保持线程池中至少存在一个工作线程
2.如果线程由于抛出用户异常终结,或者当前工作线程数,那么直接添加一个新的非核心线程
参数
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize : 核心线程池数
maximumPoolSize : 最大线程数,线程池容量
keepAliveTime : 空闲线程存活时间
unit : 时间单位
workQueue :工作队列(阻塞队列)
threadFactory :创建线程的工厂
handler :拒绝策略
工作队列:
线程池中使用的队列是 BlockingQueue
接口,常用的实现有如下几种:
- ArrayBlockingQueue:基于数组、有界,按 FIFO(先进先出)原则对元素进行排序
- LinkedBlockingQueue:基于链表,按FIFO (先进先出) 排序元素 头尾各加了reentrylock来保证能同时进出,所以吞吐量高于arrayblockingqueue
- 吞吐量通常要高于 ArrayBlockingQueue
- Executors.newFixedThreadPool() 使用了这个队列
- SynchronousQueue:不存储元素的阻塞队列
- 每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态
- 吞吐量通常要高于 LinkedBlockingQueue
- Executors.newCachedThreadPool使用了这个队列
- PriorityBlockingQueue:具有优先级的、无限阻塞队列
线程池常用实现
newCachedThreadPool 必要时创建新线程;空闲线程会被保持60秒
newFixedThreadPool 该池包含固定数量的线程;空闲线程会一直被保留
newSingleThreadExecutor 只有一个线程的“池”,该线程顺序执行每一个提交的任务(类似于Swing事件分配线程) newScheduledThreadPool 用于预定执行而构建的固定线程池,替代java.util.Timer
newSingleThreadScheduledExecutor 用于预定执行而构建的单线程“池”
newCachedThreadPoll
newCachedThreadPool方法构建了一个线程池,对于每个任务,如果有空闲线程池可用,立即让它执行任务,如果没有可用的空闲线程,则会创建一个新线程。(核心线程为0,最大是integer_maxvalue)
缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse 如果没有,就建一个新的线程加入池中
缓存型池子通常用于执行一些生存期很短的异步型任务 因此在一些面向连接的 daemon 型 SERVER 中用得不多。但对于生存期短的异步任务,它是 Executor 的首选。
能 reuse 的线程,必须是 timeout IDLE 内的池中线程,缺省 timeout 是 60s,超过这个 IDLE 时长,线程实例将被终止及移出池。
注意,放入 CachedThreadPool 的线程不必担心其结束,超过 TIMEOUT 不活动,其会自动被终止。
newFixedThreadPool
newFixedThreadPool方法构建一个具有固定大小的线程池。如果提交的任务数多余空闲的进程数,那么把得不到服务的任务放置到队列中。当其他任务完成以后再运行他们。
从方法的源代码看,cache池和fixed 池调用的是同一个底层 池,只不过参数不同:
-
fixed 池线程数固定,并且是0秒IDLE(无IDLE)。
-
cache 池线程数支持 0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60 秒 IDLE 。
newSingleThreadExecutor
newSingleThreadExecutor是一个退化了的大小为 1 的线程池:由一个线程执行提交的任务,一个接一个。
- 单例线程,任意时间池中只能有一个线程
- 用的是和 cache 池和 fixed 池相同的底层池,但线程数目是 1-1,0 秒 IDLE(无 IDLE)
拒绝策略
CallerRunsPolicy
:只要线程池没关闭,就直接用调用者所在线程来运行任务AbortPolicy
:直接抛出RejectedExecutionException
异常DiscardPolicy
:丢弃任务,没有返回和通知DiscardOldestPolicy
:把队列里待最久的那个任务扔了,然后再调用execute()
试试看能行不
submit和excute区别
excute:execute提交的方式只能提交一个Runnable的对象,且该方法的返回值是void,也即是提交后如果线程运行后,和主线程就脱离了关系了,当然可以设置一些变量来获取到线程的运行结果。并且当线程的执行过程中抛出了异常通常来说主线程也无法获取到异常的信息的,只有通过ThreadFactory主动设置线程的异常处理类才能感知到提交的线程中的异常信息。
submit:
1.提交Callable对象 Callable接口中是一个有返回值的call方法。
这种提交的方式会返回一个Future对象,这个Future对象代表这线程的执行结果
2.也可以提交一个Runable接口的对象,这样当调用get方法的时候,如果线程执行成功会直接返回null,如果线程执行异常会返回异常的信息
3.除了Runnable task之外还有一个result对象,
当线程正常结束的时候调用Future的get方法会返回result对象,当线程抛出异常的时候会获取到对应的异常的信息。