线程池的一些疑问和解答
2019独角兽企业重金招聘Python工程师标准>>>
疑问
线程池中的线程是如何实现一个线程执行多个任务的?
构造线程池时为何要用阻塞队列作为参数,非阻塞队列不行吗?
线程池的几个重要参数
corePoolSize 是线程池的核心线程数,通常线程池会维持这个线程数
maximumPoolSize 是线程池所能维持的最大线程数
keepAliveTime 和 unit 则分别是超额线程的空闲存活时间数和时间单位
workQueue 是提交任务到线程池的入队列
threadFactory 是线程池创建新线程的线程构造器
handler 是当线程池不能接受提交任务的时候的处理策略
线程池处理任务一般流程
如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;
如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
分析
addIfUnderPoolSize
以最简单的poolSize<corePoolSize的情况来分析好了,下面是关键代码
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;final ReentrantLock mainLock = this.mainLock;
mainLock.lock();try { if (poolSize < corePoolSize && runState == RUNNING)t = addThread(firstTask); //创建线程去执行firstTask任务 } finally {mainLock.unlock();
}if (t == null) return false;
t.start();return true;
}
addThread
看见了熟悉的t.start();
,但是有一个问题,任务执行完线程就应该被销毁才对,为什么线程池里的线程能够做到执行多个任务呢? t = addThread(firstTask);
里肯定大有名堂。
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w); //创建一个线程,执行任务 if (t != null) {w.thread = t; //将创建的线程的引用赋值为w的成员变量 workers.add(w); int nt = ++poolSize; //当前线程数加1 if (nt > largestPoolSize)largestPoolSize = nt;
}return t;
}
Worker
上面的代码中出现了Worker这个类,来看看Worker的结构:
private final class Worker implements Runnable {private final ReentrantLock runLock = new ReentrantLock();private Runnable firstTask;volatile long completedTasks;Thread thread;Worker(Runnable firstTask) {this.firstTask = firstTask;}boolean isActive() {return runLock.isLocked();}void interruptIfIdle() {final ReentrantLock runLock = this.runLock;if (runLock.tryLock()) {try {if (thread != Thread.currentThread())thread.interrupt();} finally {runLock.unlock();}}}void interruptNow() {thread.interrupt();}private void runTask(Runnable task) {final ReentrantLock runLock = this.runLock;runLock.lock();try {if (runState < STOP &&Thread.interrupted() &&runState >= STOP)boolean ran = false;beforeExecute(thread, task); //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据//自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等 try {task.run();ran = true;afterExecute(task, null);++completedTasks;} catch (RuntimeException ex) {if (!ran)afterExecute(task, ex);throw ex;}} finally {runLock.unlock();}}public void run() {try {Runnable task = firstTask;firstTask = null;while (task != null || (task = getTask()) != null) {runTask(task);task = null;}} finally {workerDone(this); //当任务队列中没有任务时,进行清理工作 }}
}
关于Worker这个类,有几点需要注意:
Worker实现了
runnable
接口,能作为实例化thread的参数存在,thread.start()
后执行的其实是Worker的run()
方法在Worker的
run()
方法中,有一段代码非常关键while (task != null || (task = getTask()) != null) {runTask(task);task = null;}
task!=nulll
好理解,task = getTask()) != null
又是什么意思呢?不罗嗦,继续跟代码
Runnable getTask() {for (;;) {try {int state = runState;if (state > SHUTDOWN)return null;Runnable r;if (state == SHUTDOWN) // Help drain queuer = workQueue.poll();else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,//则通过poll取任务,若等待一定的时间取不到任务,则返回nullr = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);elser = workQueue.take();if (r != null)return r;if (workerCanExit()) { //如果没取到任务,即r为null,则判断当前的worker是否可以退出if (runState >= SHUTDOWN) // Wake up othersinterruptIdleWorkers(); //中断处于空闲状态的workerreturn null;}// Else retry} catch (InterruptedException ie) {// On interruption, re-check runState}}
}
workQueue
workQueue是BlockingQueue的一个实例,而BlockingQueue的take()
方法当队列为空时会使当前线程发生阻塞
getTask()
的功能就是去缓存队列获取任务,如果缓存队列为空,则处于等待状态,否则拿到task回去执行runTask()
方法,在runTask()
方法里执行我们丢给线程池的任务的run()
方法完成任务【兜了这么一大圈子】
通过while循环使这个过程不断进行
while (task != null || (task = getTask()) != null) {runTask(task);task = null;}
总结
其实把整个流程梳理一遍就是,将要执行的任务封装进Worker对象中,Worker实现Runnable接口重写run()方法实现不断获取新任务(不管是直接获得还是来自缓存队列)的逻辑,将Worker作为参数实例化Thread对象,这样在开启线程后Worker的run方法就被间接调用了。至此,第一个问题就解决了。至于第二个问题,如果熟悉生产者消费者模式的话也不难理解。
参考资料:
Java并发编程:线程池的使用
ThreadPoolExecutor的应用和实现分析
转载于:https://my.oschina.net/u/2529036/blog/625295
线程池的一些疑问和解答相关推荐
- 线程池之工作项,等待项,计时项 (存在疑问???)
线程池函数允许我们做: 1.以异步方式调用函数 //工作项 2.每隔一段时间调用一个函数 //计时项 3.在内核对象触发时调用一个函数 //等待项 4.在异步I/O请求完成时调用一个函数 //I/O ...
- c语言 线程a每隔10秒执行一次,线程b每隔100秒执行一次,线程池执行时多线程每隔100ms执行一次线程任务 求解答...
MobileData data = listData.get(i); //data.setI(i); //Thread.sleep(100);//多线程调用接口的时候每隔100ms调用一次 //多线程 ...
- 面试官问:线程池是如何重复利用空闲的线程来执行任务的?
欢迎关注方志朋的博客,回复"666"获面试宝典 在Java开发中,经常需要创建线程去执行一些任务,实现起来也非常方便,但如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任 ...
- Java线程池 源码分析
1.个人总结及想法: (1)ThreadPoolExecutor的继承关系? ThreadPoolExecutor继承AbstractExectorService,AbstractExecutorSe ...
- 线程池是如何重复利用空闲的线程来执行任务的?
来源:blog.csdn.net/anhenzhufeng/article/details/88870374 在Java开发中,经常需要创建线程去执行一些任务,实现起来也非常方便,但如果并发的线程数量 ...
- 万字图文 | 学会Java中的线程池,这一篇也许就够了!
来源:一枝花算不算浪漫 线程池原理思维导图.png 前言 Java中的线程池已经不是什么神秘的技术了,相信在看的读者在项目中也都有使用过.关于线程池的文章也是数不胜数,我们站在巨人的肩膀上来再次梳理一 ...
- Java线程池的知识
1.为什么要用线程池 一些任务适合使用单独的线程去执行,而线程作为一项比较重的资源如果频繁创建对系统资源消耗较大.使用线程池,将线程重复使用,节省频繁创建线程的开销. 创建一个线程需要调用操作系统的A ...
- mongodb线程池_常用高并发网络线程模型设计及MongoDB线程模型优化实践
服务端通常需要支持高并发业务访问,如何设计优秀的服务端网络IO工作线程/进程模型对业务的高并发访问需求起着至关重要的核心作用. 本文总结了了不同场景下的多种网络IO线程/进程模型,并给出了各种模型的优 ...
- 线程池的一个BUG,被我发现了
欢迎关注方志朋的博客,回复"666"获面试宝典 前几天,在帮同事排查一个线上偶发的线程池错误 逻辑很简单,线程池执行了一个带结果的异步任务.但是最近有偶发的报错: java.uti ...
最新文章
- Luke 5—— 可视化 Lucene 索引查看工具,可以查看ES的索引
- 盘点CVPR 2019影响力最大的20篇论文
- 第二次作业(个人项目实践)
- 数据之光 · 安全未来 | 第四届中国数据安全治理高峰论坛圆满召开!
- 前端学成什么样就能找工作了?
- 干货| 掌握这种逻辑思维,大厂面试成功率可提升90%
- Leetcode 99. 恢复搜索二叉树
- 基于C#的MongoDB数据库开发应用(2)--MongoDB数据库的C#开发
- 阶段3 1.Mybatis_05.使用Mybatis完成CRUD_7 Mybatis中参数的深入-使用实体类的包装对象作为查询条件...
- chrome浏览器使用方法介绍
- springboot下生成复杂word文档方案 在Word软件里面制作模板
- Hive 如何设置ReduceTask的数量
- 去除input边框和去除当点击input框时显示的边框
- 如何下载建外街道卫星地图高清版大图
- Scala Case Class介绍
- 【TS TSP】基于matlab禁忌搜索求解旅行商问题【含Matlab源码 447期】
- html国内旅游计划,Web实验一 国内旅游界面
- Google PR值的详细算法
- excel做地图热力图_中国数据地图(热力图)-到市级-分档填色
- Webots+tesla+ROS2