线程池的作用:

1 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2 可以根据系统的承受能力,调整线程池中工作线程的数据,防止因为消耗过多的内存导致服务器崩溃。
使用线程池,哟啊根据系统的环境情况,手动或自动设置线程数目。少了系统运行效率补发哦,多了系统拥挤,占用内存多。用现成池控制数量,其他线程排队等候。一个任务执行完毕,再从队列中取最前面的任务开始执行,若任务中没有等待任务,线程池这一资源处于等待。当一个新任务需要运行,如果线程池中有等待的工作线程,就可以开始运行了,否则进入等待队列。

线程池类结构

结构图:

这张图基本简单代表了线程池类的结构:
1 最顶层的接口是Executor,不过Executor严格意义上来说并不是一个线程池而只是提供了一种任务如何运行的机制而已
2 ExecutorService才可以认为是真正的线程池接口,接口提供了管理线程池的方法
3下面两个分支,AbstractExecutorService分支就是普通的线程池分支,ScheduledExecutorService是用来创建定时任务的

ThreadPoolExecutor六个核心参数

下面来看一下ThreadPoolExecutor完整构造方法的签名,签名中包含了六个参数,是ThreadPoolExecutor的核心,对这些参数的理解,配置,调优对于使用好线程也是非常重要的。

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler)
参数:
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize - 池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅由保持 execute 方法提交的 Runnable 任务。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
复制代码
corePoolSize与maximumPoolSize举例详解:
  1. 池中线程数小于corePoolSize,新任务都不排队而是直接添加新进程。
  2. 池中线程数大于等于corePoolSize,workQueue未满,首选将新任务加入workQueue而不是添加新线程
  3. 池中线程数大于等于corePoolSize,workQueue已满,但是线程数小于maximumPoolSize,添加新的线程来处理被添加的任务
  4. 池中线程数大于大于corePoolSize,workQueue已满,并且线程数大于等于maximumPoolSize,新任务被拒绝,使用handler处理被拒绝的任务
    ThreadPoolExecutor通过executor(Runnable command)方法来发起一个任务的执行,通过shutdown()方法来对已经提交的任务做一个有效的关闭。尽管线程池很好,但我们要注意JDK API的一段话:
    强烈建议程序员使用较为方便的Executors工厂方法Executors.newCachedThreadPool()(无界线程池,可以进行线程自动回收),Executors.newFixedThreadPool(int)(固定大小线程池)和Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。

Executors

个人认为,线程池的重点不是ThreadPoolExecutor怎么用或者是Executors怎么用,而是在合适的场景下使用合适的线程池,所谓"合适的线程池"的意思就是,ThreadPoolExecutor的构造方法传入不同的参数,构造出不同的线程池,以满足使用的需要。

下面来看一下Executors为用户提供的几种线程池:

1、newSingleThreadExecutos() 单线程线程池

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
复制代码

单线程线程池,那么线程池中运行的线程数肯定是1。workQueue选择了无界的LinkedBlockingQueue,那么不管来多少任务都排队,前面一个任务执行完毕,再执行队列中的线程。从这个角度讲,第二个参数maximumPoolSize是没有意义的,因为maximumPoolSize描述的是排队的任务多过workQueue的容量,线程池中最多只能容纳maximumPoolSize个任务,现在workQueue是无界的,也就是说排队的任务永远不会多过workQueue的容量,那maximum其实设置多少都无所谓了

2、newFixedThreadPool(int nThreads) 固定大小线程池

public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
复制代码

固定大小的线程池和单线程的线程池异曲同工,无非是让线程池中能运行的线程编程了手动指定的nThreads罢了。同样,由于是选择了LinkedBlockingQueue,因此其实第二个参数maximumPoolSize同样也是无意义的

3、newCachedThreadPool() 无界线程池

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
复制代码

无界线程池,意思是不管多少任务提交进来,都直接运行。无界线程池采用了SynchronousQueue,采用这个线程池就没有workQueue容量一说了,只要添加进去的线程就会被拿去用。既然是无界线程池,那线程数肯定没上限,所以以maximumPoolSize为主了,设置为一个近似的无限大Integer.MAX_VALUE。 另外注意一下,单线程线程池和固定大小线程池线程都不会进行自动回收的,也即是说保证提交进来的任务最终都会被处理,但至于什么时候处理,就要看处理能力了。但是无界线程池是设置了回收时间的,由于corePoolSize为0,所以只要60秒没有被用到的线程都会被直接移除

谈谈workQueue

排队有三种通用策略:

直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

有界队列。当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

四种拒绝策略

所谓拒绝策略之前也提到过了,任务太多,超过maximumPoolSize了怎么把?当然是接不下了,接不下那只有拒绝了。拒绝的时候可以指定拒绝策略,也就是一段处理程序。

决绝策略的父接口是RejectedExecutionHandler,JDK本身在ThreadPoolExecutor里给用户提供了四种拒绝策略,看一下:

1、AbortPolicy

直接抛出一个RejectedExecutionException,这也是JDK默认的拒绝策略

2、CallerRunsPolicy

尝试直接运行被拒绝的任务,如果线程池已经被关闭了,任务就被丢弃了

3、DiscardOldestPolicy

移除最晚的那个没有被处理的任务,然后执行被拒绝的任务。同样,如果线程池已经被关闭了,任务就被丢弃了

4、DiscardPolicy

不能执行的任务将被删除

使用线程池的示例:
public class Test {public static void main(String[] args) {MyThread m1=new MyThread();MyThread m2=new MyThread();MyThread m3=new MyThread();MyThread m4=new MyThread();/*ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();newCachedThreadPool.execute(m1);newCachedThreadPool.execute(m2);newCachedThreadPool.execute(m3);newCachedThreadPool.execute(m4);newCachedThreadPool.shutdown();*/ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();newSingleThreadExecutor.execute(m1);newSingleThreadExecutor.execute(m2);newSingleThreadExecutor.execute(m3);newSingleThreadExecutor.execute(m4);newSingleThreadExecutor.shutdown();}}复制代码
shutdown和shutdownNow的区别
shutdownpublic void shutdown()
按过去执行已提交任务的顺序发起一个有序的关闭,但是不接受新任务。如果已经关闭,则调用没有其他作用。
抛出:
SecurityException - 如果安全管理器存在并且关闭此 ExecutorService 可能操作某些不允许调用者修改的线程(因为它没有 RuntimePermission ("modifyThread")),或者安全管理器的 checkAccess 方法拒绝访问。shutdownNowpublic List<Runnable> shutdownNow()
尝试停止所有的活动执行任务、暂停等待任务的处理,并返回等待执行的任务列表。在从此方法返回的任务队列中排空(移除)这些任务。
并不保证能够停止正在处理的活动执行任务,但是会尽力尝试。 此实现通过 Thread.interrupt() 取消任务,所以无法响应中断的任何任务可能永远无法终止。返回:
从未开始执行的任务的列表。
抛出:
SecurityException - 如果安全管理器存在并且关闭此 ExecutorService 可能操作某些不允许调用者修改的线程(因为它没有 RuntimePermission ("modifyThread")),或者安全管理器的 checkAccess 方法拒绝访问。复制代码

可以调用线程池的shutdown或者shutdownNow方法来关闭线程池。它们的原理是遍历线程池的工作线程,然后诸葛调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能有缘无法停止。
区别: shutdown执行平缓的关闭过程而shutdownNow将执行粗暴的关闭过程。

转载于:https://juejin.im/post/5c0bd0c6e51d450329730df6

一篇就让你懂线程池原理相关推荐

  1. 一文搞懂线程池原理——Executor框架详解

    文章目录 1 使用线程池的好处 2 Executor 框架 2.1 Executor 框架结构 2.2 Executor 框架使用示意图 2.3 Executor 框架成员 2.3.1 Executo ...

  2. 一起学JAVA之【基础篇】4种默认线程池介绍

    一起学JAVA之[基础篇]4种默认线程池介绍 默认线程池创建方式 java.util.concurrent 提供了一个创建线程池的工具类Executors,里面有四种常用的线程池创建方法 public ...

  3. python3 线程池源码解析_5分钟看懂系列:Python 线程池原理及实现

    概述 传统多线程方案会使用"即时创建, 即时销毁"的策略.尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数极其频繁,那么服务器 ...

  4. dict实现原理 python_5分钟看懂系列:Python 线程池原理及实现

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 概述 传统多线程方案会使用"即时创建, 即时销毁"的策略.尽管与创建进程相比,创 ...

  5. Java多线程闲聊(四):阻塞队列与线程池原理

    Java多线程闲聊(四)-阻塞队列与线程池原理 前言 复用永远是人们永恒的主题,这能让我们更好地避免重复制造轮子. 说到多线程,果然还是绕不开线程池,那就来聊聊吧. 人们往往相信,世界是存在一些规律的 ...

  6. JAVA线程池原理以及几种线程池类型介绍

    在什么情况下使用线程池? 1.单个任务处理的时间比较短      2.将需处理的任务的数量大 使用线程池的好处: 1.减少在创建和销毁线程上所花的时间以及系统资源的开销      2.如不使用线程池, ...

  7. Java高并发编程详解系列-线程池原理自定义线程池

    之前博客的所有内容是对单个线程的操作,例如有Thread和Runnable的使用以及ThreadGroup等的使用,但是对于在有些场景下我们需要管理很多的线程,而对于这些线程的管理有一个统一的管理工具 ...

  8. java并发包线程池原理分析锁的深度化

    java并发包&线程池原理分析&锁的深度化 并发包 同步容器类 Vector与ArrayList区别 1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素 ...

  9. python线程池原理_Python定时器线程池原理详解

    这篇文章主要介绍了Python定时器线程池原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 定时器执行循环任务: 知识储备 Timer(int ...

  10. Tomcat线程池监控及线程池原理分析

      目录         一.背景         二.tomcat线程池监控         三.tomcat线程池原理         四.总结 一.背景 我们都知道稳定性.高可用对于一个系统来讲 ...

最新文章

  1. rails中对应关系
  2. 漏洞评估的优先级决定了网络安全保护的成本
  3. 笔记-VUE滚动加载更多数据
  4. 【opencv有趣应用】图像拼图
  5. java sts安装步骤_java开发工具STS的下载及安装
  6. 华为网络技术大赛笔记——数据库基础原理
  7. android 获取本地视频文件以及缩略图
  8. java中CAE画实心圆的参数_java绘图中RenderingHints 参数
  9. 读书笔记-人际网络的中心
  10. Va02 修改数量和价格条件时报错
  11. 美通企业日报 | 人们对传统教育系统的信心正在动摇;90后渴望健康却管不住嘴迈不开腿...
  12. P1714 切蛋糕(线段树+前缀和)
  13. 淘宝购物折扣秒杀分享群淘宝红包怎么抢
  14. 我的世界服务器自动刷矿机,我的世界刷矿机MOD
  15. uefi+guid分区与legacy+mbr分区_硬盘分区表格式GUID和MBR知识普及
  16. win 下通过dos命令格式化磁盘
  17. HTML中插入地图的方法
  18. rust卡领地柜权限_rust一个领地柜有多大范围 | 手游网游页游攻略大全
  19. c语言教学系统管理,在Moodle平台中实现_C语言_课程教学管理系统
  20. html+css+javascript代码编程规范之CSS

热门文章

  1. vs2015未能正确加载“ProviderPackage”包
  2. 自然语言处理要解决的问题
  3. 美化博客园 添加网易云音乐及生成文章目录
  4. 自定义控件的构建(6)
  5. 【leetcode_easy】590. N-ary Tree Postorder Traversal
  6. MUI框架-11-MUI前端 +php后台接入百度文字识别API
  7. ROS中测试机器人里程计信息
  8. 2017中国大学生程序设计竞赛 - 女生专场C【前后缀GCD】
  9. 网络爬虫(2)--异常处理
  10. 2014-03-18