第九章 Java中线程池

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方法,也可以在任务执行前、执行后和线程池关闭前执行一些代码来进行监控。

原文地址:https://www.cnblogs.com/hzzjj/p/9740136.html

时间: 2024-11-07 07:54:38

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

java中线程池

在jdk1.5中加入了java.util.concurrent,这个包中主要介绍java中线程以及线程池的使用 现在看怎么创建一个线程池,在java中Executors类创建线程池的,而线程池分为三类, 1:Executors.newFixedThreadPool(3);//这是固定的线程池 2: Executors.newCachedThreadPool();创建一个带缓存的线程池,比如创建了3个线程在线程池中,但是现在有10个线程在执行任务,因此它还会创建7个线程 3:Executors.n

关于Java中线程池的解读

之前的面试中多次被问到线程池的相关内容,所以在之后的时间内我仔细的学习了一下线程池的相关内容. 1.使用线程池的意义 复用:类似WEB服务器等系统,长期来看内部需要使用大量的线程处理请求,而单次请求响应时间通常比较短,此时Java基于操作系统的本地调用方式大量的创建和销毁线程本身会成为系统的一个性能瓶颈和资源浪费.若使用线程池技术可以实现工作线程的复用,即一个工作线程创建和销毁的生命周期期间内可以执行处理多个任务,从而总体上降低线程创建和销毁的频率和时间,提升了系统性能. 流控:服务器资源有限,

JAVA中线程池的简单使用

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

java中线程池创建的几种方式

java中创建线程池的方式一般有两种: 通过Executors工厂方法创建 通过new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)自定义创建 Executors工厂方法创建 package com.javaBase.LineDistancePond; import java.util.

java中线程池的理解和使用

看过很多关于线程的文章,但还是觉得需要说些什么. 我只想说使用线程池的时候,就不要自己写队列和循环等东西了. 代码如下: 1 public class SqlLogRecorder { 2 private static Logger logger = LoggerFactory.getLogger(SqlLogRecorder.class); 3 4 public SqlLogRecorder(MSConfigManager configManager){ 5 // 可以根据需要创建不同类型的连

Java并发编程中线程池源码分析及使用

当Java处理高并发的时候,线程数量特别的多的时候,而且每个线程都是执行很短的时间就结束了,频繁创建线程和销毁线程需要占用很多系统的资源和时间,会降低系统的工作效率. 参考http://www.cnblogs.com/dolphin0520/p/3932921.html 由于原文作者使用的API 是1.6 版本的,参考他的文章,做了一些修改成 jdk 1.8版本的方法,涉及到的内容比较多,可能有少许错误. API : jdk1.8.0_144 ThreadPoolExecutor类 Java中线

《Java并发编程的艺术》 第9章 Java中的线程池

第9章 Java中的线程池 在开发过程中,合理地使用线程池能带来3个好处: 降低资源消耗.通过重复利用已创建的线程 降低线程创建和销毁造成的消耗. 提高响应速度.当任务到达时,任务可以不需要等到线程创建就能立即执行. 提高线程的可管理性.线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配.调优和监控. 9.1 线程池的实现原理 当提交一个新任务到线程池时,线程池的处理流程如下: 1)线程池判断核心线程池里的线程是否都在执行任务.如果不是,则创建

深入理解Java之线程池

原作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利. 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 那么有没有一种办法使得线程可

Java(Android)线程池 总结

一种是使用Executors工厂生产线程池:另一种是直接使用ThreadPoolExecutor自定义. Executors工厂生产线程池 Java(Android)线程池 Trinea 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行一个异步任务你还只是如下new Thread吗? Java 1 2 3 4 5 6 7 newThread(newRunnable(){ @Ove