文章目录

  1. 线程生命周期的开销:线程比较少的情况使用new Thread(task)无多大影响,但是如果涉及到线程比较多的情况,应用的性能就会受到影响,如果jdbc创建连接一样,new Thead创建线程也会耗资源、耗时间的。
  2. 资源的消耗量:活动线程会消耗系统性能,如果运行的线程数量多余可用的处理器数,那么就会有大量空闲的线程占用内存,会给垃圾收集器带来压力,如果有cpu资源竞争,还会有其他性能开销。
  3. 限定创建线程的数目:如果不设定创建线程的数量,一个任务一个线程无限创建线程,高负载情况下就有可能造成OutOfMemoryError错误。所以像tomcat这种servlet容器的线程池都设置了最大线程数量的。

Executor框架组成

Eexecutor接口:包含Eexecutor、ExecutorService、ScheduledExecutorService
ThreadPool线程池:包含ThreadPoolExecutor、ScheduledThreadPoolExecutor
Fork/Join框架:JDK1.7新增
类之间的关系如下:

Executor框架将线程的创建与执行解耦,可以异步调用,让任务相互独立,用阻塞队列管理任务,直接在当前线程中消费队列,可以减少线程之间进行资源竞争,也可以减少线程的创建和系统的开销,要廉价多了。这种设计就是经典并发模式Active Object Models(也称Actor Models)的实现,如下图:

可以参考execute方法,就按照上面的模式来的。另外Executors工厂类创建了不同的连接池,为任务的执行分配了不同执行策略。

线程池

concurrent包里面主要包含ThreadPoolExecutor、ScheduledThreadPoolExecutor两种线程池,Executors类提供了很多创建线程池的方法

,newFixedThreadPool创建定长的线程池、newWorkStealingPool创建ForkJoinPool(jdk1.8新增)、newCachedThreadPool创建缓存线程池(可以回收空闲的线程)、newSingleThreadExecutor创建当个线程池(保证FIFOLIFO优先级),newScheduledThreadPool创建定时器线程池,如果有特殊处理的,也可以根据自己的需求来创建连接池,如tomcat也是基于ThreadPoolExecutor实现了自己的连接池。

ThreadPoolExecutor

  1. public (int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue) {
  6. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  7. Executors.defaultThreadFactory(), defaultHandler);
  8. }

从ThreadPoolExecutor最简单的构造函数可以看出,线程池总是依赖阻塞队列而工作的,newFixedThreadPool与newSingleThreadExecutor使用的是LinkedBlockingQueue无界队列,newCachedThreadPool使用SynchronousQueue,ThreadPoolExecutor还定义了一个Worker执行任务线程,除此之外,还有个非常重要的变量ctl(线程池控制状态)由执行器状态和工作线程的数量组成,在控制执行的时候都是围绕这个变量来判断。

处理任务

参考前面的Active Object Models图,可以将线程池执行任务主要分为3个步骤:
1、如果任务少于线程池大小时,就作为firstTask分别创建工作线程执行任务
2、如果第n个任务超出了线程池大小,就加入到阻塞队列,并从新检测执行器状态状态以及工作线程数量(有可能线程发生RuntimeException挂掉,最后可工作线程数量变为0),如果工作线程挂完了就重新启动一个线程(解决线程泄露问题)。阻塞队列的任务执行就在第一步所创建的线程执行,参考代码:

  1. public void execute(Runnable command) {
  2. if (command == null)
  3. throw new NullPointerException();
  4. int c = ctl.get();
  5. if (workerCountOf(c) < corePoolSize) {
  6. if (addWorker(command, true))
  7. return;
  8. c = ctl.get();
  9. }
  10. if (isRunning(c) && workQueue.offer(command)) {
  11. int recheck = ctl.get();
  12. if (! isRunning(recheck) && remove(command))
  13. reject(command);
  14. else if (workerCountOf(recheck) == 0)
  15. addWorker(null, false);
  16. }
  17. else if (!addWorker(command, false))
  18. reject(command);
  19. }
  20. final void runWorker(Worker w) {
  21. Thread wt = Thread.currentThread();
  22. Runnable task = w.firstTask;
  23. w.firstTask = null;
  24. w.unlock(); // allow interrupts
  25. boolean completedAbruptly = true;
  26. try {
  27. //判断当前任务或者检测阻塞队列
  28. while (task != null || (task = getTask()) != null) {
  29. w.lock();
  30. // If pool is stopping, ensure thread is interrupted;
  31. // if not, ensure thread is not interrupted. This
  32. // requires a recheck in second case to deal with
  33. // shutdownNow race while clearing interrupt
  34. if ((runStateAtLeast(ctl.get(), STOP) ||
  35. (Thread.interrupted() &&
  36. runStateAtLeast(ctl.get(), STOP))) &&
  37. !wt.isInterrupted())
  38. wt.interrupt();
  39. try {
  40. beforeExecute(wt, task);
  41. 大专栏  concurrent包分析之Executor框架 Throwable thrown = null;
  42. try {
  43. task.run();
  44. } catch (RuntimeException x) {
  45. thrown = x; throw x;
  46. } catch (Error x) {
  47. thrown = x; throw x;
  48. } catch (Throwable x) {
  49. thrown = x; throw new Error(x);
  50. } finally {
  51. afterExecute(task, thrown);
  52. }
  53. } finally {
  54. task = null;
  55. w.completedTasks++;
  56. w.unlock();
  57. }
  58. }
  59. completedAbruptly = false;
  60. } finally {
  61. processWorkerExit(w, completedAbruptly);
  62. }
  63. }

3、如果任务入队列失败了,就重新开启一个线程去执行,如果还是失败了,就可以确定是执行器关闭了或者线程池已经达到饱和状态了。

线程池原理

从上面的执行步骤来就可以看出线程池依赖于阻塞队列,然后基于生产者消费者模式实现的,execute()方法一直添加任务(生产者),当任务数量超出线程池的最大长度就添加到阻塞队列等待排队执行,而第一次创建的所有工作线程(最大数量为线程池的最大长度)就会一直判断线程池里面是否有任务执行,如果有就执行任务(消费者)。这样做就可以重用线程了,不用每次去创建线程,性能肯定比一个任务一个线程好多了。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor由ScheduledFutureTask、DelayedWorkQueue组成,实现任务执行还是用的父类ThreadPoolExecutor的工作线程。jdk1.5之前使用定时任务都用的Timer,但是与ScheduledThreadPoolExecutor有着明显区别:

  1. Timer使用的System.currentTimeMillis()毫秒来控制时间,ScheduledThreadPoolExecutor使用System.nanoTime()纳秒控制更加精准,并且可以使用TimeUnit进行时间的跨单元转换。
  2. Timer只有单个工作线程,ScheduledThreadPoolExecutor可以配置多个工作线程。
  3. 如果工资线程发生异常,Timer会造成线程泄露没有重启的线程,ScheduledThreadPoolExecutor会一直检测工作线程的数量,如果没有工作线程了,会一直添加数量小于线程池的工作线程(使用父类ThreadPoolExecutor的addaddWorker方法)
    从以上比较可以看出,ScheduledThreadPoolExecutor就是取代Timer的。

还是从一个简单的例子看ScheduledThreadPoolExecutor是如何工作的。addWork参考领导/跟随者模式

  1. @Test
  2. public void testScheduleAtFixedRate() throws Exception {
  3. ScheduledThreadPoolExecutor p = new ScheduledThreadPoolExecutor(1);
  4. CountDownLatch latch = new CountDownLatch(3);
  5. p.scheduleAtFixedRate(new Task(latch), 0, 1000, TimeUnit.MILLISECONDS);
  6. latch.await();
  7. }

线程池都是基于生产者消费者模式的,所以ScheduledThreadPoolExecutor也不例外,执行任务的时候会一直像队列里面添加任务:

  1. private void delayedExecute(RunnableScheduledFuture<?> task) {
  2. if (isShutdown())
  3. reject(task);
  4. else {
  5. //添加到队列,让工作线程去消费
  6. super.getQueue().add(task);
  7. if (isShutdown() &&
  8. !canRunInCurrentRunState(task.isPeriodic()) &&
  9. remove(task))
  10. task.cancel(false);
  11. else
  12. //启动线程
  13. ensurePrestart();
  14. }
  15. }

ScheduledThreadPoolExecutor第一次执行用的父类ThreadPoolExecutor的addWorker方法添加工作线程并启动它,然后每个工作线程会检查队列里面是否有消费的线程,参考ThreadPoolExecutor的runWorker方法,与ThreadPoolExecutor有点不同的是,ScheduledThreadPoolExecutor使用的是延时阻塞队列DelayedWorkQueue。每次消费就进行take操作:

  1. public RunnableScheduledFuture<?> take() throws InterruptedException {
  2. final ReentrantLock lock = this.lock;
  3. lock.lockInterruptibly();
  4. try {
  5. for (;;) {
  6. RunnableScheduledFuture<?> first = queue[0];
  7. if (first == null)
  8. available.await();
  9. else {
  10. long delay = first.getDelay(NANOSECONDS);
  11. if (delay <= 0)
  12. //返回队列中的执行任务之前,要先执行finally模块中的唤醒操作
  13. return finishPoll(first);
  14. first = null; // don't retain ref while waiting
  15. if (leader != null)
  16. available.await();
  17. else {
  18. //领导线程初始化为null,当前线程为线程池中的线程
  19. Thread thisThread = Thread.currentThread();
  20. leader = thisThread;
  21. try {
  22. //当前线程等待延迟时间到期
  23. available.awaitNanos(delay);
  24. } finally {
  25. if (leader == thisThread)
  26. leader = null;
  27. }
  28. }
  29. }
  30. }
  31. } finally {
  32. if (leader == null && queue[0] != null)
  33. available.signal();
  34. lock.unlock();
  35. }
  36. }

参考资料:

concurrent包分析之Executor框架的更多相关文章

  1. jdk8中java.util.concurrent包分析

    并发框架分类 1. Executor相关类 Interfaces. Executor is a simple standardized interface for defining custom th ...

  2. Google-Guava Concurrent包里的Service框架浅析

    原文地址  译文地址 译者:何一昕 校对:方腾飞 概述 Guava包里的Service接口用于封装一个服务对象的运行状态.包括start和stop等方法.例如web服务器,RPC服务器.计时器等可以实 ...

  3. java.util.concurrent包学习笔记(一)Executor框架

    类图: 其实从类图我们能发现concurrent包(除去java.util.concurrent.atomic 和 java.util.concurrent.locks)中的内容并没有特别多,大概分为 ...

  4. JDK源码分析之concurrent包(一) -- Executor架构

    Java5新出的concurrent包中的API,是一些并发编程中实用的的工具类.在高并发场景下的使用非常广泛.笔者在这做了一个针对concurrent包中部分常用类的源码分析系列.本系列针对的读者是 ...

  5. JAVA基础之——JDK包分析concurrent

    concurrent在哪儿:jdk\jre\lib\rt.jar package java.util.concurrent; 本文从特性.分类.扩展方面一一道来. 1 特性 包中包含大量有用的构建块, ...

  6. 并发工具箱 concurrent包的原理分析以及使用

    1.java.util.concurrent 包下的类分类图 locks部分:显式锁(互斥锁和速写锁)相关: atomic部分:原子变量类相关,是构建非阻塞算法的基础: executor部分:线程池相 ...

  7. Java:concurrent包下面的Map接口框架图(ConcurrentMap接口、ConcurrentHashMap实现类)

    Java集合大致可分为Set.List和Map三种体系,其中Set代表无序.不可重复的集合:List代表有序.重复的集合:而Map则代表具有映射关系的集合.Java 5之后,增加了Queue体系集合, ...

  8. Java:concurrent包下面的Collection接口框架图( CopyOnWriteArraySet, CopyOnWriteArrayList,ConcurrentLinkedQueue,BlockingQueue)

    Java集合大致可分为Set.List和Map三种体系,其中Set代表无序.不可重复的集合:List代表有序.重复的集合:而Map则代表具有映射关系的集合.Java 5之后,增加了Queue体系集合, ...

  9. JDK源码分析之concurrent包(四) -- CyclicBarrier与CountDownLatch

    上一篇我们主要通过ExecutorCompletionService与FutureTask类的源码,对Future模型体系的原理做了了解,本篇开始解读concurrent包中的工具类的源码.首先来看两 ...

随机推荐

  1. leetcode腾讯精选练习之除自身以外数组的乘积(十)

    最长公共前缀 题目 给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积. ...

  2. UEFI启动(翻译)

    本文是我翻译自国外技术博客的一篇文章,其中讲述了 UEFI 的一些基本概念和细节. 本文的原始链接位于: https://www.happyassassin.net/2014/01/25/uefi-b ...

  3. Patroni 修改配置

    Patroni 修改配置 背景 使用 Patroni 部署 postgresql 集群的时候,不能单独修改单点的配置,这里需要通过 Patroni 来修改配置. 修改步骤 1. 修改 postgres ...

  4. Andorid Studio 3.1 引入第三方jar包的方法

    今天手痒,把android studio 从3.0升级到3.1.2 发现很多问题 1.发现编译会下载好多东西,等半天 2.之前的jar包导不进来了 针对jar包导入不成功,我查了一下说是不支撑comp ...

  5. JavaScript下判断元素是否存在

    1. 判断表单元素是否存在(一) if("periodPerMonth" in document.theForm) { return true; } else{ return fa ...

  6. platform 平台驱动——设备的写作流程

    说明:在内核源码里会有很多已经实现的驱动,对于我们来说只需要写好设备文件即可,但是我们如何知道驱动需要那些数据,以及有哪些驱动呢? 解决: 1.首先在内核源码目录下执行执行菜单配置命令: make m ...

  7. shell_跳板机推送公钥

    #!/bin/bash#push publickey to aap-servers#将局域网内可以ping通的主机ip保存到一个文件> ip_up.txtfor i in {2..10}do { ...

  8. 吴裕雄--天生自然C语言开发:内存管理

    #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { ]; char *d ...

  9. ipv6 mac地址转化为linklocal地址

    mac  3c:32:66:67:dd:46 linklocal地址 前六位固定  fe80:: 第七位  mac地址第一个byte进行如下计算 (byte) ((byte) (macbyte &am ...

  10. PAT甲级——1012 The Best Rank

    PATA1012 The Best Rank To evaluate the performance of our first year CS majored students, we conside ...