项目用到线程池,但是其实很多人对原理并不熟悉 ,这里只是整理一下

ThreadPoolExecutor

  java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类

  • 构造方法
  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue,
  6. ThreadFactory threadFactory) {
  7. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  8. threadFactory, defaultHandler);
  9. }
  • 参数

    corePoolSize               核心线程数
    maximumPoolSize            最大线程数 阻塞队列装不下的后 总得线程数  包含corePoolSize 
    keepAliveTime             超时时间 线程池中当前的空闲线程服务完某任务后的存活时间。如果时间足够长,那么可能会服务其它任务
    unit               时间单位
    workQueue             阻塞队列 线程数大于核心线程后放到队列中
    threadFactory         线程池工厂
    handler              拒绝策略 阻塞队列满了,也达到了最大线程数 执行拒绝策略

    • corePoolSize:核心池的大小,在创建了线程池后,即在没有任务到来之前就创建  默认情况下,在创建了线程池后,线程池中的线程数为0,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法来预创建线程.当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
    • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
  • 流程

    1)当池子大小 小于corePoolSize就新建线程,并处理请求
    2)当池子大小 等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去从workQueue中取任务并处理
    3)当workQueue放不下新入的任务时,新建线程入池,并处理请求(不用等待队列),如果池子大小撑到了maximumPoolSize就用RejectedExecutionHandler来做拒绝处理
    4)另外,当池子的线程数大于corePoolSize的时候,多余的线程会等待keepAliveTime长的时间,如果无请求可处理就自行销毁

    处理步骤:  核心线程 << 阻塞队列 <<最大线程数

  • 通俗流程解释

    假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;
    当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;
    如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;
    然后就将任务也分配给这4个临时工人做;
    如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。
    当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的.
    这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。

    也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。

  • 阻塞队列

    1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;

    2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;

    3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务

  • 拒绝策略 四种

    AbortPolicy 默认 直接抛弃 并抛异常
    DiscardPolicy 直接抛弃 不抛异常
    CallerRunsPolicy 在主线程中执行
    DiscardOldestPolicy 把注册队列中最老的抛弃掉 执行当前的
    自定义的策略 实现RejectedExecutionHandler即可

  • 测试案例
  1. public class Test {
  2. public static void main(String[] args) {
  3. ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
  4. new ArrayBlockingQueue<Runnable>(5));
  5.  
  6. for(int i=0;i<15;i++){
  7. MyTask myTask = new MyTask(i);
  8. executor.execute(myTask);
  9. System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
  10. executor.getQueue().size()+",已执行完别的任务数目:"+executor.getCompletedTaskCount());
  11. }
  12. executor.shutdown();
  13. }
  14. }
  15.  
  16. class MyTask implements Runnable {
  17. private int taskNum;
  18.  
  19. public MyTask(int num) {
  20. this.taskNum = num;
  21. }
  22.  
  23. @Override
  24. public void run() {
  25. System.out.println("正在执行task "+taskNum);
  26. try {
  27. Thread.currentThread().sleep(4000);
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. System.out.println("task "+taskNum+"执行完毕");
  32. }
  33. }
  • 执行结果
  1. 正在执行task 0
  2. 线程池中线程数目:1,队列中等待执行的任务数目:0,已执行完别的任务数目:0
  3. 线程池中线程数目:2,队列中等待执行的任务数目:0,已执行完别的任务数目:0
  4. 正在执行task 1
  5. 线程池中线程数目:3,队列中等待执行的任务数目:0,已执行完别的任务数目:0
  6. 正在执行task 2
  7. 线程池中线程数目:4,队列中等待执行的任务数目:0,已执行完别的任务数目:0
  8. 正在执行task 3
  9. 线程池中线程数目:5,队列中等待执行的任务数目:0,已执行完别的任务数目:0
  10. 正在执行task 4
  11. 线程池中线程数目:5,队列中等待执行的任务数目:1,已执行完别的任务数目:0
  12. 线程池中线程数目:5,队列中等待执行的任务数目:2,已执行完别的任务数目:0
  13. 线程池中线程数目:5,队列中等待执行的任务数目:3,已执行完别的任务数目:0
  14. 线程池中线程数目:5,队列中等待执行的任务数目:4,已执行完别的任务数目:0
  15. 线程池中线程数目:5,队列中等待执行的任务数目:5,已执行完别的任务数目:0
  16. 线程池中线程数目:6,队列中等待执行的任务数目:5,已执行完别的任务数目:0
  17. 正在执行task 10
  18. 线程池中线程数目:7,队列中等待执行的任务数目:5,已执行完别的任务数目:0
  19. 正在执行task 11
  20. 线程池中线程数目:8,队列中等待执行的任务数目:5,已执行完别的任务数目:0
  21. 正在执行task 12
  22. 线程池中线程数目:9,队列中等待执行的任务数目:5,已执行完别的任务数目:0
  23. 正在执行task 13
  24. 线程池中线程数目:10,队列中等待执行的任务数目:5,已执行完别的任务数目:0
  25. 正在执行task 14
  26. task 3执行完毕
  27. task 0执行完毕
  28. task 2执行完毕
  29. task 1执行完毕
  30. 正在执行task 8
  31. 正在执行task 7
  32. 正在执行task 6
  33. 正在执行task 5
  34. task 4执行完毕
  35. task 10执行完毕
  36. task 11执行完毕
  37. task 13执行完毕
  38. task 12执行完毕
  39. 正在执行task 9
  40. task 14执行完毕
  41. task 8执行完毕
  42. task 5执行完毕
  43. task 7执行完毕
  44. task 6执行完毕
  45. task 9执行完毕
  • 如何合理的配置线程池

    • 要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:

      1. 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
      2. 任务的优先级:高,中和低。
      3. 任务的执行时间:长,中和短。
      4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。

      任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池。IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

      优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

      执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。

      依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。

      建议使用有界队列,有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点,比如几千。有一次我们组使用的后台任务线程池的队列和线程池全满了,不断的抛出抛弃任务的异常,通过排查发现是数据库出现了问题,导致执行SQL变得非常缓慢,因为后台任务线程池里的任务全是需要向数据库查询和插入数据的,所以导致线程池里的工作线程全部阻塞住,任务积压在线程池里。如果当时我们设置成无界队列,线程池的队列就会越来越多,有可能会撑满内存,导致整个系统不可用,而不只是后台任务出现问题。当然我们的系统所有的任务是用的单独的服务器部署的,而我们使用不同规模的线程池跑不同类型的任务,但是出现这样问题时也会影响到其他任务。

  • 最佳实践
  1. //创建线程池指定有意义的线程名字, 方便出错是回溯
  2. ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build();
  3. ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
  5.  
  6. singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName())
  7. );
  8.  
  9. public class TimerTaskThread extends Thread {
  10. public TimerTaskThread(){
  11. super.setName("TimerTaskThread");

  12. }

  13. }

线程池ThreadPoolExecutor整理的更多相关文章

  1. 硬核干货:4W字从源码上分析JUC线程池ThreadPoolExecutor的实现原理

    前提 很早之前就打算看一次JUC线程池ThreadPoolExecutor的源码实现,由于近段时间比较忙,一直没有时间整理出源码分析的文章.之前在分析扩展线程池实现可回调的Future时候曾经提到并发 ...

  2. java线程池ThreadPoolExecutor使用简介

    一.简介线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:ThreadPoolExecutor(int corePoolSize, int m ...

  3. 线程池ThreadPoolExecutor

    线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为: ThreadPoolExecutor(int corePoolSize, int maxi ...

  4. 关于线程池ThreadPoolExecutor使用总结

    本文引用自: http://blog.chinaunix.net/uid-20577907-id-3519578.html 一.简介 线程池类为 java.util.concurrent.Thread ...

  5. [转] 引用 Java自带的线程池ThreadPoolExecutor详细介绍说明和实例应用

    PS: Spring ThreadPoolTaskExecutor vs Java Executorservice cachedthreadpool 引用 [轰隆隆] 的 Java自带的线程池Thre ...

  6. android线程池ThreadPoolExecutor的理解

    android线程池ThreadPoolExecutor的理解 线程池 我自己理解看来.线程池顾名思义就是一个容器的意思,容纳的就是ThreadorRunable, 注意:每一个线程都是需要CPU分配 ...

  7. 线程池ThreadPoolExecutor使用简介

    一.简介 线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为: ThreadPoolExecutor(int corePoolSize, int ...

  8. 线程池ThreadPoolExecutor使用简介(转)

    一.简介 线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为: ThreadPoolExecutor(int corePoolSize, int ...

  9. java线程API学习 线程池ThreadPoolExecutor(转)

    线程池ThreadPoolExecutor继承自ExecutorService.是jdk1.5加入的新特性,将提交执行的任务在内部线程池中的可用线程中执行. 构造函数 ThreadPoolExecut ...

随机推荐

  1. mac下载的excel如果带有超链接,url被转义问题

    注释的代码是file开头的,这种链接在mac系统进行跳转url会转义 hyperlink 还有一种就是http这种就可以正常跳转了. String sLink = basePath + "/ ...

  2. 使用wxpy来实现自动发送消息统计微信好友信息的功能

    发送消息太频繁会出现禁言消息 1:导入wxpy模块 pip install wxpy pip3 install wxpy #二者选一 调用模块 # 导入模块 from wxpy import * # ...

  3. LeetCode题解之Binary Tree Paths

    1.题目描述 2.分析 使用递归. 3.代码 vector<string> ans; vector<string> binaryTreePaths(TreeNode* root ...

  4. python第七十七天---HTML

    HTML5 :规则, 浏览器的通用规则 1.规则, 浏览器的通用规则 2.开发者: 学习html 规则 开发后台程序 - 写html文件 (当作模板) - 数据库获取数据,替换到指定的HTML文件中的 ...

  5. 面向对象的封装与隐藏 this

    当我们创建一个对象的时候,我们可以通过‘对象.属性’的方式,对对象的属性进行赋值. 这里赋值操作要受到属性的数据类型和存储范围的制约,但是除此之外,没有其他制约条件. 但是实际问题中我们需要给这个属性 ...

  6. 获取目录文件.bat

    @echo off & setlocal EnableDelayedExpansion for /f "delims=" %%i in ('"dir /a/s/b ...

  7. 4.3Python数据类型(3)之字符串类型

    返回总目录 目录: 1.字符串的概念 2.字符串的形式 3.字符串的转义符 4.字符串一般操作 5.字符串函数操作 (一)字符串的概念 由单个字符组成的一个集合 (二)字符串的形式 双引号与单引号的效 ...

  8. Linux系统将http转为https

    想把网站由http访问转变为https访问并没有想象中那么难,网上查了一些资料,想要转为https需要SSL安全证书,这里推荐一款景安网络的证书,可以免费试用一年时间,自己拿来实践还是很不错的选择. ...

  9. 请教MAC OS下PHP的mcrypt怎么安装

    安装方法一: 通过Homebrew安装mcrypt,安装成功 [Shell] 纯文本查看 复制代码 brew install mcrypt MCrypt是一个功能强大的加密算法扩展库,它包括有22种算 ...

  10. 转:git合并冲突解决方法

    git合并冲突解决方法 1.git merge冲突了,根据提示找到冲突的文件,解决冲突 如果文件有冲突,那么会有类似的标记 2.修改完之后,执行git add 冲突文件名 3.git commit注意 ...