线程池
线程 & 进程
概念
进程是操作系统进行资源分配和调度的一个独立单元,而线程是CPU调度的基本单元。
关联与区别
- 同一个进程可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程。
- 线程是轻量级的进程,它的创建和销毁所需要的时间比进程要小很多,并且所有操作系统的执行功能都是创建线程去执行的。
- 线程中执行一般都要进行互斥或同步,因为它们共享同一进程的所有资源。
- 都有自己的私有属性用来标识一个线程或进程的标志。
线程池
定义
- 一块缓存了一定线程数量的区域
作用
- 重用线程:降低因线程的创建和销毁所带来的性能开销
- 管理线程:统一分配、调优、监控(优化执行顺序、控制最大并发数避免阻塞)以提高线程的响应速度和执行效率
构造方法
方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
参数解析
- corePoolSize:核心线程数(一般情况下会一直存活)
- maximumPoolSize: 线程池所能容纳的最大线程数(达到此值得后续线程会被阻塞)
- keepAliveTime:非核心线程的超时时长(当系统中非核心线程的的闲置时间超过此值,则会被回收。注意,当ThreadPoolExecutor中的allowCoreThreadTImeOut属性设置为true时,此值也表示核心线程的超时时长
- unit:keepAliveTime的单位
- workQueue:线程池中的任务队列(主要用来存储已经被execute方法提交但是尚未执行的任务),一般有以下几种实现方式:
1、ArrayBlockingQueue(一个规定了大小的BlockingQueue)
2、LinkedBlockingQueue(大小不确定的BlockingQueue,采用FIFO存取顺序)
3、PriorityBlockingQueue(大小不确定的BlockingQueue,采用Comparator存取顺序)
4、SynchronousQueue(一种无缓冲的等待队列)
5、DelayQueue(常用管理一个超时未响应的连接队列)
详细介绍参考Java多线程-工具篇-BlockingQueue - threadFactory:线程工厂(用于为线程池创建新的线程)
- handler:阻塞处理(当线程和队列达到界定值,执行被阻塞时的处理程序,默认情况下,当线程池无法处理新线程时或抛出一个RejectExecutionException)
主要方法
execute()
执行
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();
}
//判断线程池是否为RUNNING状态,并且将任务添加至队列中.
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//审核下线程池的状态,如果不是RUNNING状态,直接移除队列中
if (! isRunning(recheck) && remove(command))
reject(command);
//如果当前线程数量为0,则单独创建线程,而不指定任务.
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//如果不满足上述条件,尝试创建一个非核心线程来执行任务,如果创建失败,调用reject()方法.
else if (!addWorker(command, false))
reject(command);
}
submit()
本质还是execute(),不过加了返回值
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
//还是通过调用execute
execute(ftask);
//最后会将包装好的Runable返回
return ftask;
}
//将Callable<T> 包装进FutureTask中
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
//可以看出FutureTask也是实现Runnable接口,因为RunableFuture本身就继承了Runnabel接口
public class FutureTask<V> implements RunnableFuture<V> {
.......
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
相关方法
reject()
线程创建失败执行
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
addWorker()
创建线程核心方法
private boolean addWorker(Runnable firstTask, boolean core) {
//类似goto,是Java的标识符,在这里出现是为了防止在多线程的情况下,compareAndIncrementWorkerCount(),计算线程池状态出现问题,而设立重试的关键字.
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//看似判断条件很麻烦
//分拆后主要两点
//线程已经处于STOP或者即将STOP的状态
//或者 处于SHUTDOWN状态,并且传递的任务为null,此时队列不为空还需要增加线程,除了这种情况,其他情况都不需要增加线程
//以上的情况就不需要
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//判断当前工作线程数量是否超过最大值
//或者当前工作线程数量超过 核心线程数或者最大线程数,这个值根据第二个布尔变量决定
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//这段函数是判断 线程池状态的统计更新成没成功
//如果成功直接跳出这个循环,继续执行
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
//如果不成功则跳到外层循环入口,重新执行.
retry inner loop
}
}
//下面是创建线程的过程,并且在创建线程的过程中加锁
//Worker就是线程的一个包装类.
//这里分别对线程的创建成功和失败分别做出了处理.
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 rs = runStateOf(ctl.get());
//从这里可以看出线程池创建线程,只会在两种情况下创建:
//1.线程池在RUNNING状态(rs<SHUTDOWN)
//2.线程池处于SHUTDOWN状态,并且任务为null,但是此时任务队列不为空,需要继续增加线程来加快处理进度.
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//在这里就是先检查下Thread状态,防止意外发生.
if (t.isAlive())
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);
}
//如果添加成功就返回true,否则添加失败就返回false.
return workerStarted;
}
addWorkedFailed()
在addWorker()方法中,如果线程创建之后,没有最终运行(workerStarted=false)这时候会调用addWorkedFailed()方法.
/**
* 回滚工作线程的创建操作:
* 1.如果线程的包装类Worker存在,就将其remove掉.
* 2.remove掉添加线程失败的Worker,需要刷新当前工作线程的数量
* 3.尝试终止操作,并且终止这个线程的操作.
*/
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
//尝试停止操作.
tryTerminate();
} finally {
mainLock.unlock();
}
}
tryTerminate()
在addWorker()方法中,如果线程创建之后,没有最终运行(workerStarted=false)这时候会调用addWorkedFailed()方法.
注意:
如果发生以下两种情况,使用该方法将会将线程池转换为终止状态(TERMINATED):
1、SHUTDOWN状态下,队列为空的情况下.
2、STOP状态下.
如果符合上述条件,可以转换终止状态时,这时会中断当前线程池内空闲的线程,以确保终止的信号的传递.
interruptIdleWorkers()
/**
*onlyOne:true只中断一个,false中断所有
**/
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();
}
}
//如果onlyOne参数为True,则只执行一次就跳出.
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
shutdown()
中断所有空闲的线程,它的核心方法还是调用interruptIdleWorkers()方法.
注意:
1、在shutdown()执行时可以让现有的任务被执行,但是新的任务不在会被处理.
2、如果已经是SHUTDOWN状态,那么继续调用不会产生任何效果.
3、这个方法不会等待所有任务都执行完在提交,如果想先让所有任务先完成请使用awaitTermination()方法.
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//校验线程的状态
checkShutdownAccess();
//设置线程池状态为SHUTDOWN
advanceRunState(SHUTDOWN);
//中断所有空闲进程.调用的interruptIdleWorkers(false);
interruptIdleWorkers();
//需要自己实现,在中断所有线程可定制的操作
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
awaitTermination()
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
//设置时间
long nanos = unit.toNanos(timeout);
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//这是死循环,当线程池的状态为TERMINATED时,跳出循环返回true,也就是所有任务都完成.否则超时或者线程中断则返回false.
while (!runStateAtLeast(ctl.get(), TERMINATED)) {
if (nanos <= 0L)
return false;
nanos = termination.awaitNanos(nanos);
}
return true;
} finally {
mainLock.unlock();
}
}
shutdownNow()
中断所有空闲的线程,并尝试关闭正在运行的线程
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP);
interruptWorkers();
//将任务队列移除进入一个新的列表
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
allowsCoreThreadTimeOut()
允许核心线程被回收
public boolean allowsCoreThreadTimeOut() {
return allowCoreThreadTimeOut;
}
常用线程池
1. FixedThreadPool
所有的线程都是核心线程,且线程的超时时间为0,说明核心线程即使在没有任务可执行的时候也不会被回收(这样可让FixedThreadPool更快速的响应请求),最后的线程队列是一个LinkedBlockingQueue,但是LinkedBlockingQueue却没有参数,这说明线程队列的大小为Integer.MAX_VALUE(2的31次方减1),当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来.
//特点:
//核心线程数和最大线程数相同.
//无超时时间
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()
);
2. CacheThreadPool
CachedTreadPool一个最大的优势是它可以根据程序的运行情况自动来调整线程池中的线程数量,它是有线程超时机制的,超时时间为60秒,CachedThreadPool中是没有核心线程的,但是它的最大线程数却为Integer.MAX_VALUE,由于最大线程数为无限大,所以每当我们添加一个新任务进来的时候,如果线程池中有空闲的线程,则由该空闲的线程执行新任务,如果没有空闲线程,则创建新线程来执行任务,值得注意的是当整个线程都处于闲置状态时,线程池中的线程都会超时而被停止,但是这时候的CacheThreadPool几乎不占任何系统资源的.它比较适合执行大量的耗时较少的任务.
//无核心线程,并且最大线程数为int的最大值.
//超时时间为60s
//队列为SynchronousQueue同步阻塞队列,队列中没有任何容量.只有在有需求的情况下,队列中才可以试着添加任务.
public static ExecutorService newCacheThreadPool(){
return new ThreadPoolExecutor(
0,Integer.MAX_VALUE,
60L,TimeUnit.SECONDS,
new SynchronousQueue<Runnable>()
);
}
3. ScheduledThreadPool
它的核心线程数量是固定的,而非核心线程数是没有限制的,并且当非核心线程闲置时会被立即回收.主要用来执行定时任务和具有固定周期的重复任务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSzie) {
return new ScheduledThreadPoolExecutor(corePoolSzie);
}
//核心线程数是固定的,非核心线程无限大,并且非核心线程数有10s的空闲存活时间
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
示例:
//延迟启动任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable(){
@Override
public void run() {
Log.d("google_lenve_fb", "run: ----");
}
};
scheduledExecutorService.schedule(runnable, 1, TimeUnit.SECONDS);
//延迟定时执行任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable(){
@Override
public void run() {
Log.d("google_lenve_fb", "run: ----");
}
};
scheduledExecutorService.scheduleAtFixedRate(runnable, 1, 1, TimeUnit.SECONDS);
//延迟执行任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
Runnable runnable = new Runnable(){
@Override
public void run() {
Log.d("google_lenve_fb", "run: ----");
}
};
scheduledExecutorService.scheduleWithFixedDelay(runnable, 1, 1, TimeUnit.SECONDS);
4. SingleThreadExecutor
singleThreadExecutor和FixedThreadPool很像,不同的就是SingleThreadExecutor的核心线程数只有1个,不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作,文件操作等
public static ExecutorService newSingleThreadExecutor() {
return Executors.newSingleThreadExecutor();
}
//特点:
//线程中只有一个核心线程
//并且无超时时间
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
引用&摘抄:
http://blog.youkuaiyun.com/l540675759/article/details/62230562
http://blog.youkuaiyun.com/u012702547/article/details/52259529
https://juejin.im/post/5a974960f265da4e7071f9ec?utm_source=gold_browser_extension