第9章 Java中的线程池 第10章 Exector框架
与新建线程池相比线程池的优点
线程池的分类
ThreadPoolExector参数、执行过程、存储方式
阻塞队列
拒绝策略
10.1 Exector框架简介
10.1.1 Executor框架的两级调度模型
Exector框架目的是提高Java使用线程执行异步任务的效率,核心思想是把工作单元和执行机制分开,在此之前工作单元和执行机制的角色都由Java中的线程来扮演,在此之后执行机制由Exector来提供。主线程创建实现Runnable或者Callable接口的对象封装需要执行的任务,这一点和传统的多线程机制相同,但是创建完相应的对象后不是执行执行新建的线程而是交给Exector框架去具体的启动线程,Exector返回实现Future接口的对象作为执行结果。
传统的调度方式称为“一级调度框架”(我起的),新建的线程直接交给OS执行,线程执行完毕后销毁。两级调度框架主线程只是把“任务”交给Executor,具体启动线程、何种方式启动线程、执行完毕后线程如何处理等需要和OS打交道的事情由Executor来处理。

下面使用一个简单的线程池的使用例子。把新建MyTask类实现Runnable接口并把需要执行的代码写在run方法里。在主线程main方法里新建了大小为5的线程池并向线程池里添加了10个任务。
public class ThreadDemo {
public static class MyTask implements Runnable{
@Override
public void run() {
System.out.println(System.currentTimeMillis() + ":Thread Id" + Thread.currentThread().getId());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyTask myTask = new MyTask();
ExecutorService threadPool = Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++){
threadPool.submit(myTask);
}
}
}
结果如下,可以看到两个特点:1、前五个任务和后五个线程的ID相同,说明线程池的线程是复用的。2、前五个和后五个执行时间相差5S,说明本次使用的线程池是固定大小的
1552285178203:Thread Id10
1552285178203:Thread Id11
1552285178203:Thread Id9
1552285178203:Thread Id12
1552285178203:Thread Id13
1552285179208:Thread Id10
1552285179208:Thread Id9
1552285179209:Thread Id13
1552285179209:Thread Id12
1552285179209:Thread Id11
10.1.2 Executor框架的成员
正如Spring框架,Executor框架在架构上是由一些最简单的接口串起来,下面按照创建工作单元,交给框架,得到返回结果的顺序介绍Executor的成员。
(1) Runnable Callable接口
实现上述两个接口都要重写run方法,所有需要执行的逻辑都写在run方法里,实现了这两个接口的对象就是工作单元,所有工作单元都可以交给Executor框架去执行。他们的区别在于Callable接口有返回值。
(2) Executor接口

最上层是Executor接口,仅仅提供了execute方法。其次是ExecutorService接口,扩展了Executor接口提供了更多方法如各种各样终止线程池的方法。再接着是一个抽象类AbstractExecutorService,该抽象类提供了一些供子类使用的方法如submit方法,该方法会以某种方式调用executor方法。最后才遇到了最重要的类ThreadPoolExecutor,上面示例代码使用的FixedThreadPool实际上就是ThreadPoolExecutor。Executor下各种各样不同的可以直接使用的线程池类都是采用不同的入参创建的ThreadPoolExecutor和ScheduledThreadPoolExecutor。
(3) Future 接口

Future接口及其实现类是用来表示计算结果,但使用方式并不是我猜想的的在计算结束后把结果用Future实现类包装起来,关于Futur接口的使用可以看一下AbstractExecutorService的submit方法。submit方法的重载方法一共有三个,不管入参的类型如何在submit方法里总是把Runnable或者Callable的对象封装成RunnableFuture的对象ftask,而Executor框架真正处理的对象就是RunnableFuture对象,RunnableTask是一个接口,真的的对象类型是FutureTask。
FutureTask像一个运输工具,载着Runnable Callable进Executor框架,载着返回结果出来。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
10.2 ThreadPoolExecutor
10.2.1 ctl与内部状态
ctl是用来表示线程池的运行状态和线程池中任务状态的AtomicInteger变量。前三位用来表示线程池的状态共有五中状态
- Running 线程池处在运行状态,可以向线程池加入新的任务,线程池也会处理线程池里的任务
- Shutdown 线程池不接受新的任务,但是会处理原有剩余的任务
- Stop 撂挑子,及不接受新任务,也不处理原有任务
- TIDYING 当线程池“任务”数量为0的时候会变为TIDYING状态,当变为TIDYING状态时为执行terminated方法用于线程终止前的后事处理。该方法默认为空需要用户根据需要重写
- TERMINATED 在TIDYING运行完terminated后来到的状态

10.2.2 关键参数与执行流程

- corePoolSize 线程池新接受一个任务时,如果当前线程池worker的数目少于corePoolSize,无论当前的Worker是否存在空闲都要新建一个Worker。
- 如果当前Worker的数目大于corePoolSize,就把新任务加入workQueue中。再把任务加入队列后还要进行两次检查,如果当前队列已经不处于running则从队列移除出去;如果当前线程数目等于零那么就新建线程,注意这里新建的线程传入的command参数为null。
- 如果workQueue不是无界队列,那么当该队列满的时候回创建新的worker
- 如果超过了maximumPoolSize,那么执行拒绝策略
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);
}
这种设计思路似的线程池在绝大多数工作中第二种状态,即新的任务在队列里等待,等到线程空余下来去执行他们,省略了线程创建和销毁的代价。线程池中的线程在执行完新建该线程传进来的任务后会反复从队列里取出待执行的任务并执行。
addWorker是整个线程池最核心的方法用于向线程池里增加线程,刚方法一共做了三件事情:
- 判断是否可以创建新的线程。首先根据线程池的状态判断,其次根据当前线程池拥有的线程的数目判断
- CAS 修改线程池里线程的数目
- 新建woker对象加入队列并启动worker对象。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
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;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
final ReentrantLock mainLock = this.mainLock;
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
int rs = runStateOf(c);
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;
}
一个worker启动的时候会首先执行新建该worker时传入的任务,如果该任务为Null就会调用getTask方法去队列里取任务并执行。整个过程是一个循环如果这个循环结束那么worker会被销毁,即当getWorker返回为Null的时候worker会被销毁。getWorker会到队列里不停的取任务,取任务的过程如果超时那么getWorker就会返回null即worker被销毁。
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 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();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
10.3 派生出的线程池
通过传入不同的参数ThreadPoolExecutor可以变成不同策略的线程池,主要关注的参数有核心线程数目和最大线程数目、阻塞队列的种类、超时。
10.3.1 FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
新建fixedThreadPool的时候需要传入nThreads,该参数即是核心线程数目也是最大线程数目,这意味着当等待队列满的时候直接执行拒绝策略,但其传入的队列是一个无界队列所以这种情况并不存在。其次超时时间为0,意味着当worker一旦空闲就会立即销毁。无界队列和超时时间为0的组合使得一旦线程池没有任务整个线程池就会停止。并且无界队列由于永远不会满,所以不会执行拒绝策略。
10.3.2 SingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
新建的时候有一个线程,也就是说是一个线程在循环往复的工作。并且采用的是无界队列。
10.3.3 CachedThreadPool
第9章 Java中的线程池 第10章 Exector框架的更多相关文章
- 《Java并发编程的艺术》 第9章 Java中的线程池
第9章 Java中的线程池 在开发过程中,合理地使用线程池能带来3个好处: 降低资源消耗.通过重复利用已创建的线程 降低线程创建和销毁造成的消耗. 提高响应速度.当任务到达时,任务可以不需要等到线程创 ...
- 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!
碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...
- JAVA中创建线程池的五种方法及比较
之前写过JAVA中创建线程的三种方法及比较.这次来说说线程池. JAVA中创建线程池主要有两类方法,一类是通过Executors工厂类提供的方法,该类提供了4种不同的线程池可供使用.另一类是通过Thr ...
- Java中的线程池用过吧?来说说你是怎么理解线程池吧?
前言 Java中的线程池用过吧?来说说你是怎么使用线程池的?这句话在面试过程中遇到过好几次了.我甚至这次标题都想写成[Java八股文之线程池],但是有点太俗套了.虽然,线程池是一个已经被说烂的知识点了 ...
- 浅析Java中的线程池
Java中的线程池 几乎所有需要异步或并发执行任务的程序都可以使用线程池,开发过程中合理使用线程池能够带来以下三个好处: 降低资源消耗 提高响应速度 提高线程的可管理性 1. 线程池的实现原理 当我们 ...
- java 中的线程池
1.实现下面的一个需求,控制一个执行函数只能被五个线程访问 package www.weiyuan.test; public class Test { public static void main( ...
- Java中的线程池
package com.cn.gbx; import java.util.Date; import java.util.Random; import java.util.Timer; import j ...
- java 中的线程池和线程 调用小demo
public class Main { public static void main(String[] args) { try { /// ThreadPoolExecutor executor = ...
- Java中的线程池ExecutorService
示例 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.u ...
随机推荐
- SpringBoot的打包失败
打包是根据pom.xml文件来打的包. 如果使用Maven的默认打包,只会将src/main下的java和resources里面的内容打进Jar包中. 通过maven-assembly-plugin这 ...
- 异常:Data = 由于代码已经过优化或者本机框架位于调用堆栈之上,无法计算表达式的值。
做项目的时候,将DataTable序列化成Json,通过ashx向前台返回数据的时候,前台总是获取不到数据,但是程序运行却没问题, 没抛出异常.一时找不到办法,减小输出的数据量,这时前台可以接收到页面 ...
- 封装个 Android 的高斯模糊组件
本篇文章已授权微信公众号 hongyangAndroid (鸿洋)独家发布 最近基于 Android StackBlur 开源库,根据自己碰到的需求场景,封装了个高斯模糊组件,顺便记录一下. 为什么要 ...
- es6 语法 (map、set和array 的对比)
//数据结构对比 增查改删 { //map和array对比 let map = new Map(); let array = []; //增 map.set('t',1); array.push({t ...
- 【代码笔记】Web-JavaScript-JavaScript正则表达式
一,效果图. 二,代码. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- Nodejs全局/缓存路径配置
$ npm config set prefix "D:\Program Files\nodejs\node_global" $ npm config set cache " ...
- XBanner的简单使用轮播
导入依赖 implementation 'jp.wasabeef:glide-transformations:3.0.1' implementation 'com.xhb:xbanner:1.2.2' ...
- 使用混淆ProGuard压缩代码和资源/减少方法数量
ProGuard介绍 ProGuard是一个Java类文件压缩器,优化器,混淆器和预先文件验证器. 压缩步骤检测和删除未使用的类,字段,方法和属性. 优化步骤分析和优化方法的字节码. 混淆步骤使用短无 ...
- android 圆角背景
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http: ...
- 【Spring源码解读】bean标签中的属性
说明 今天在阅读Spring源码的时候,发现在加载xml中的bean时,解析了很多标签,其中有常用的如:scope.autowire.lazy-init.init-method.destroy-met ...