Java中的线程池是运用场景最多的并发框架,几乎所有需求异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理地使用线程池能够带来3个好处。

  1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  2. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行
  3. 提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

线程池的实现原理

当向线程池提交一个任务之后,线程池是如何处理这个任务呢?如图

从图中可以看出,当提交一个新任务到线程池时,线程池的处理流程如下

(1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程

(2)线程池判断工作队列是否已经满。如果工作队里没有满,则将新提交的任务存储在这个工作队列里,满了则交给饱和策略来处理这个任务

(3)线程池判断线程池的线程数量是否已满。如果没有,则创建一个新的工作线程来执行任务任务,反之交给饱和策略来处理这个任务

ThreadPoolExecutor执行execute()方法的示意图如下

ThreadPoolExecutor执行execute方法分下面4种情况:

(1)如果当前运行线程少于corePoolSize,则创建新线程来执行任务(执行这一步需要获取全局锁)

(2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue

(3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(执行这一步需要获取全局锁)

(4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法

ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize)。几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁

线程池的使用

线程池的创建

我们可以通过ThreadPoolExecutor来创建一个线程池:

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, KeepAliveTime, timeUnit, runnableTaskQueue, threadFactory, rejectedExecutionHandler);

创建一个线程池需要输入几个参数如下:

(1)corePoolSize(核心池大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于等于线程池的核心线程数就不在创建。调用prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程

(2)runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列:

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO原则对元素进行排序
  • LinkedBlockingQueue:是一个基于链表结构的阻塞队列,也是按照FIFO排序元素。吞吐量通常要高于ArrayBlockingQueue
  • SynchronousBlocking:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列

(3)maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数,如果队列满了,并且已创建的线程数小于最大线程数,则线程池会在创建新的线程执行任务。如果是无界的任务队列该参数没有效果

(4)threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字

(5)rejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一个策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK.5中提供以下4种策略

  • AbortPolicy:直接抛出异常
  • CallerRunsPolicy:只用调用者所在线程来运行任务
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
  • DiscardPolicy:不处理,丢弃掉

当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化存储不能处理的任务。

(6)KeepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率

(7)timeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微妙(MICROSECONDS,千分之一毫秒)、纳秒(NANOSECONDS,千分之一微妙)

向线程池提交任务

可以使用两个方法向线程池提交任务,分别为execute()和submit()方法

execute方法用于提交不需要返回值的任务,所以无法判断任务是否被线程执行成功

submit方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程知道任务完成,而使用get(long timeout, TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完

关闭线程池

可以通过调用线程池的shutdown和shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是他们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所以的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdow只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于应该调用哪一种方法,应该由提交到线程池的任务特性决定。通常调用shutdown方法来关闭线程池,如果任务不一定执行完,则可以调用shutdownNow方法。

合理地配置线程池

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

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

性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置可能小的线程。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。

注意:如果一直有优先级搞的任务提交到队里里,那么优先级低的任务可能永远不能执行

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

线程池的监控

可以通过线程池提供的参数进行监控:

  • taskCount:线程池需要执行的任务数量
  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount
  • largestPoolSize:线程池里曾经创建过的最大线程数量
  • getPoolSize:线程池的线程数量
  • getActiveCount:获取活动的线程数

通过扩展线程池进行监控。可以通过继承线程池来自定义线程池,重写线程池的beforeExecute、afterExecute和terminated方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。

第九章 Java中线程池的更多相关文章

  1. Java中线程池,你真的会用吗?

    在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Executors静态工厂构建线程池,但一般不建 ...

  2. 沉淀再出发:java中线程池解析

    沉淀再出发:java中线程池解析 一.前言 在多线程执行的环境之中,如果线程执行的时间短但是启动的线程又非常多,线程运转的时间基本上浪费在了创建和销毁上面,因此有没有一种方式能够让一个线程执行完自己的 ...

  3. Java中线程池,你真的会用吗?ExecutorService ThreadPoolExcutor

    原文:https://www.hollischuang.com/archives/2888 在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及 ...

  4. Java中线程池的学习

    线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理.当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程 ...

  5. java中线程池的使用方法

    1 引入线程池的原因 由于线程的生命周期中包括创建.就绪.运行.阻塞.销毁阶段,当我们待处理的任务数目较小时,我们可以自己创建几个线程来处理相应的任务,但当有大量的任务时,由于创建.销毁线程需要很大的 ...

  6. Java中线程池的实现原理

    知识点总结 ---------------------------------------------------------------------------------------------- ...

  7. Java中线程池的实现原理-求职必备

    jdk1.5引入Executor线程池框架,通过它把任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池,而不用关心该任务是如何执行.被哪个线程执行,以及什么时候执行. 初始化线程池(4种) ...

  8. JAVA中线程池的简单使用

    比如现在有10个线程,但每次只想运行3个线程,当这3个线程中的任何一个运行完后,第4个线程接着补上.这种情况可以使用线程池来解决,线程池用起来也相当的简单,不信,你看: package com.dem ...

  9. java中线程池的几种实现方式

    1.线程池简介:    多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.        假设一个服务器完成一项任务所需时间为:T1 创建 ...

随机推荐

  1. iBatis.net 第一篇 搭建

    iBatis.net 和 iBatis 是有区别的,一个是在net下使用的,另一个是在java下使用. 要想使用,需要下载相关iBatis.net包 1.下载iBatis.net,在网上有很多下载cs ...

  2. jsp连接sqlServer数据库教程、jsp连接sqlServer数据库报ClassNotFoundException异常

    jsp连接sqlServer数据库教程: 首先讲下我用的工具版本以供参考: jar包:jtds1.3.1.jar  下载地址:点击进入 数据库:SQL Server2012 服务器:Tomcat8.0 ...

  3. Android当下最流行的开源框架总结

    Android中能够简化开发流程的一些框架 本文介绍的是一些博主在开发过程中经常用到的Android开源框架,所谓开源框架我的理解就是别人封装好的代码,可以直接拿过来使用,并且源码也全部公开的代码库. ...

  4. 用urliso把linux刻录U盘失败无数次。 用unetbootin试试可以启动的。

    我用的是ubuntu 16.04 lts 画面挺好用的.

  5. if (i%4 ==0 ) 逻辑的魅力 在于 这里

    <div class="course_height"> </div> <script src="js/jquery.min.js" ...

  6. hive_学习_01_hive环境搭建(单机)

    一.前言 本文承接上一篇:hbase_学习_01_HBase环境搭建(单机),主要是搭建 hive 的单机环境 二.环境准备 1.说明 hive 的下载来源有: 官方版本:http://archive ...

  7. JavaScript--收藏栏添加按钮,放大hdu题目字体

    觉得HDOJ的题目字体太小了,一波小操作 在收藏栏添加:添加网页->网址改为: javascript: void((function() { var element = document.get ...

  8. uva1152 - 4 Values whose Sum is 0(枚举,中途相遇法)

    用中途相遇法的思想来解题.分别枚举两边,和直接暴力枚举四个数组比可以降低时间复杂度. 这里用到一个很实用的技巧: 求长度为n的有序数组a中的数k的个数num? num=upper_bound(a,a+ ...

  9. 3.MySQL优化---单表查询优化的一些小总结(非索引设计)

    整理自互联网.摘要: 接下来这篇是查询优化.其实,大家都知道,查询部分是远远大于增删改的,所以查询优化会花更多篇幅去讲解.本篇会先讲单表查询优化(非索引设计).然后讲多表查询优化.索引优化设计以及库表 ...

  10. linux命令学习笔记( 2 ) : cd 命令

    Linux cd 命令可以说是Linux中最基本的命令语句,其他的命令语句要进行操作,都是建立在使用 cd 命令上的. 所以,学习Linux 常用命令,首先就要学好 cd 命令的使用方法技巧. . 命 ...