您的位置:首页 > 编程语言 > Java开发

Java并发之线程池详解

2017-11-29 00:00 489 查看
Java线程池有哪些状态?如何切换?

线程池的种类有哪些?

创建线程池需要哪些参数?含义?

将任务添加到线程池的运行流程?

线程池怎么重用线程?

线程池如何关闭?

首先线程池有哪些状态以及如何切换

来搞一下源码:ThreadPoolExecutor.java

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;

各种状态的说明在源码的注释中都有,我们来看一下:

The runState provides the main lifecycle control, taking on values:

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


处于RUNNING状态的线程池可以接收新的任务,也能够处理阻塞队列里面的任务。

SHUTDOWN:不能接受新的任务,可以处理阻塞队列里面的任务。

STOP:不能接受新的任务,也不会处理阻塞队列的任务。而且中断正在处理的任务。

TIDYING:所有的任务都被终止,线程池中工作线程的数量为0,将要调用terminated()方法。

TERMINATED: terminated()方法调用完毕,终止状态。

状态之间进行切换:

RUNNING -> SHUTDOWN
On invocation of shutdown(), perhaps implicitly in finalize()
(RUNNING or SHUTDOWN) -> STOP
On invocation of shutdownNow()
SHUTDOWN -> TIDYING
When both queue and pool are empty
STOP -> TIDYING
When pool is empty
TIDYING -> TERMINATED
When the terminated() hook method has completed

RUNNING切换到SHUTDOWN状态:调用shutdown()方法后切换到SHUTDOWN状态。

RUNNING、SHUTDOWN切换到STOP状态:调用shutdownNow()方法切换到STOP状态

SHUTDOWN切换到TIDYING状态:在阻塞队列和线程池为空的情况下切换到TIDYING状态。

STOP切换到TIDYING状态:线程池为空时切换到该状态。

TIDYING切换到TERMINATED状态:
调用terminated()方法切换到该状态。

创建线程池需要哪些参数?参数的含义?

源码中创建ThreadPoolExecutor的构造方法是这样的:

public ThreadPoolExecutor
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {

corePoolSize:
核心线程的数量,当一个任务条件到线程池中执行时,只要线程的数量达不到corePoolSize的数量,就会创建新的线程,直到线程数量达到corePoolSize的大小,不再创建,将新的任务放到阻塞队列中等待执行。
maximumPoolSize:线程池中允许创建最大线程数量,如果阻塞队列已满,创建的线程数量小于maximumPoolSize的话,会创建新的线程来处理阻塞队列中的任务。
keepAliveTime:线程活动的保持时间,当线程数量大于corePoolSize时,核心线程之外的线程空闲时间,大于该时间将会被回收。

unit:keepAliveTime的单位。

workQueue: 阻塞队列,当线程池中的线程数量为corePoolSize时,新来的任务会放到该阻塞队列中,有四种阻塞队列:

ArrayBlockingQueue:基于数组的阻塞队列
LinkedBlockingQueue:基于链表的阻塞队列
SychronusBlockingQueue:不存储元素的阻塞队列(没见过)
PriorityBlockingQueue:基于优先级的阻塞队列

threadFactory:创建线程的工厂

handler: 当阻塞队列满了,没有空闲线程的情况下,线程中的数量已经达到了最大数目,必须采取一种策略来处理新来的任务。主要有四种策略:AbortPolicy(直接抛出异常)、CallerRunsPolicy(使用调用者的线程处理)、DiscardOldsPolciy(丢弃阻塞队列中一个任务,处理当前任务)、Discard直接丢弃。

线程池的运行流程?

线程池对象有两个方法用来执行线程:
submit()和execute()方法。

区别是submit()方法可以传入一个实现Callable()接口的对象,在当前任务结束的时候能返回一个Future对象来获取任务的返回值。
submit()方法还是调用了execute()方法

public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}

下面我们重点来看一下execute()方法:

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线程

如果当前线程数量大于corePoolSize的数量,尝试添加任务到阻塞队列里面,然后第二次检查线程的数量,如果线程池状态不在RUNNING,直接移除,同时拒绝当前请求的任务,如果状态为RUNNING且线程的数量为0,创建一个新的线程。

如果当前状态不是RUNNING,则尝试创建一个新的worker来处理任务,如果创建失败,拒绝当前任务。

线程运行的四个阶段:
1.poolSize<corePoolSize
当前线程数量小于核心线程数量,直接创建线程来处理

2.poolSize=corePoolSize,而且此时阻塞队列没有满,将此任务添加到阻塞队列里面,如果此时存在工作线程(非核心线程),由工作线程来处理阻塞队列中的任务,如果工作线程数量为0,则会创建工作线程来处理
3. poolSize=corePoolSize 并且此时阻塞队列满了,直接创建新的工作线程来处理当前任务

4.poolSize=maxmumPoolSize:此时阻塞队列也满了,就会触发拒绝机制,具体什么策略由传入的RejectExceptionHandler决定

重用线程

线程池重用线程,主要是在执行worker线程时候去阻塞队列里面拿任务,不断的去拿任务交给线程来执行,达到重用的目的,直到getTask为null为止。

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) {

线程池关闭

调用shutdown()和shutdownNow()方法

Java中常用线程池:

<p>newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。</p>
<p>newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。</p>
<p>newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。</p>
<p>newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。</p>
<p>newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。</p>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: