java 线程池那点事儿
1.为什么要用线程池
线程池提供了一种任务的提交与任务的执行解偶的策略,并且有以下几种优势
提高资源利用率
通过复用自己的线程来降低创建线程和销毁线程的开销。
如果不用线程池而采用为每个任务都创建一个新线程的话会很浪费系统资源。因为创建线程和销毁线程都是耗系统资源的行为。除此之外还会由于线程过多而导致JVM出现OutOfMemory
提高响应速度
当新来一个任务时,如果有空闲线程存在可立即执行任务,中间节省了创建线程的过程
统一管理线程
如果不用线程池来管理,而是无限创建线程的话不仅消耗系统资源,而且还会导致系统不稳定。使用线程池可以进行统一分配,调优以及监控。
2.常见线程池以及参数
2.1 创建线程池
通过Executors的工厂方法来创建线程池,比如创建一个固定线程数的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
通过ThreadPoolExecutor的构造函数来创建线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(int corePoolSize,
int maxmumPoolSize,
long keepAliveTime,
TimeUnit unit,
BolockingQueue<Runnable> workQueue,
ThreadFactory factory,
RejectedExecusionHandler handler);
官方比较推荐使用后者,因为可以灵活选择参数配置,以及自定义配置
无论是采用那种方式创建一个线程池,它内部都是通过 ThreadPoolExecutor的构造函数来实现的。所以要想全面掌握线程池的各种特性以及性能的话需要对 ThreadPoolExecutor类深入理解,包括各种参数的意义,参数之间是如何配合工作的等等。
2.2 线程池参数
corePoolSize 核心线程数。默认情况下,当提交一个任务时线程池会新建一个线程池(及时有空闲线程存在),直到线程数量等于基本大小(也可以预初始化线程)
workQueue 一个存放任务的阻塞队列,当线程池里的基本线程都在忙着的时候,提交一个新任务的话会暂时存放到阻塞队列,等待执行,常用的阻塞队列有一下几种
- ArrayBlockingQueue 基于数组实现的FIFO阻塞队列(有限长度)
- LinkedBlockingQueue 基于链表实现的FIFO阻塞队列(无限长度)
- SynchronousQueue 本身不存储任务,当有任务来的时候会一直阻塞,直到线程去执行
- PriorityBlockingQueue 具有优先级的优先队列
maximumPoolSize 最大线程数。当所有的核心线程都在运行并且阻塞队列也满了的话会创建额外的几个线程来执行,这时候线程池里的所有线程数量就是最大线程数量。如果线程池采用的是像LinkedBlockingQueue这种无界队列的话,该参数不会起到作用
KeepAliveTime 和 TimeUnit 如果某个线程的空闲时间大于KeepAliveTime的时候会被标记为可回收, 并且当前线程池里的数量大于核心线程数量的时候会被终止。所以该参数跟maximumPoolSize一样,使用无界队列的时候不起作用,TimeUnit是时间单位
RejectedExecusionHandler 饱和策略。如果我们的任务队列满了,并且线程池里的线程数量已经达到了最大线程数,而且这些线程都不再空闲状态。这时候新提交任务的话,无法去执行,所以需要一种饱和策略来去处理这些任务。Java提供了一下几种策略
- AbortPolicy (默认策略) 直接抛异常,调用者需要根据自己的需求去捕获处理异常
- DiscardPolicy 直接丢弃,不处理直接丢弃该任务
- DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务。如果是优先队列的话会丢弃优先级最高的任务,所以不推荐与优先队列一起组合使用
- CallerRunsPolicy 由调用者当前线程来执行该任务。
2.3 常见线程池
Java类库提供了灵活的创建线程池方法,可以通过调用Executors中的静态工厂方法来创建一个线程池。
- newFixedThreadPool 将创建一个固定线程数量的线程池,每当提交一个任务时就创建一个线程。直到达到线程池的最大数量。corePoolSize和maximumPoolSize值相同,并且采用了LinkedBlockingQueue,所以最大线程池数,饱和策略,存活时间等等参数都将不被用到。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- newSingleThreadExecutor 一个但线程的Executor,它跟上面的newFixedThreadPool类似,区别在于只有一个工作线程。通过该线程池可以确保有序的执行队列中的任务,因为只有一个工作线程,所以出队的顺序就是执行的顺序。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- newCachedThreadPool 此线程池的线程数量没有显式的限制默认为Integer.MAX_VALUE,可以为每个任务创建一个线程来处理,但是它没这么做,它是具有缓存线程的功能,是一种可伸缩的。比如,当处理新任务是首先看有没有空闲可用的线程来处理,如果没有的话才会新创建。并且当创建的线程空闲一段时间之后会回收(默认一分钟)
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- newScheduledThreadPool
创建一个固定线程数的线程池,并且以延迟或定时的方式来执行。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
3.执行流程
线程池执行任务是由Executor接口的execute()方法来执行的,下面看下执行任务流程的一段核心代码(java8)
public void execute(Runnable command) {
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
线程池的执行流程由以下几个步骤来完成(每种线程池的实现略有不同,但核心思想相似)
1)首先,如果当前工作的线程数量小于核心线程数,则新创建一个线程来执行。如果核心线程池数满了,
2)判断线程池的任务队列是否已满,如果没满的话把该任务放到任务队列里,等待核心线程空闲之后去执行,如果满了的话进入下一阶段
3)判断判断线程池里工作的线程数量是否达到了最大线程数,如果没达到则创建一个新的线程来去执行。否则,采用饱和策略来处理
如果进去看内部实现的话会发现,线程池会把每个工作线程包装成Worker,而把需要执行的任务包装成Task来运行。
4.健康检查
在项目中使用线程池的时候有必要对线程池进行监控,这样可以根据线程池的使用状况快速定位问题。可以通过线程池提供的参数进行监控,在监控线程池的时候可以使用以下属性。
- taskCount 线程池需要执行的任务数量
- completedTaskCount 已经成功运行的任务数
- largestPoolSize 曾经出现的最多线程数
- getPoolSize 获取线程数
- getActiveCount 活动线程数
示例代码
public class Task implements Runnable{
private int i;
public Task(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println("task num ="+i);
}
}
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(10,20,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
for (int i=0;i<5;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成任务数= "+pool.getCompletedTaskCount()+" 任务数= "+pool.getTaskCount()+" " +
""+" 活动线程数="+pool.getActiveCount() +"" +" 线程数="+pool.getPoolSize() +" "+" 最大线程数="+pool.getLargestPoolSize() );
Task task = new Task(i);
pool.execute(task);
}
pool.shutdown();
}
}
打印结果如下:
完成任务数= 0 任务数= 0 活动线程数=0 线程数=0 最大线程数=0
task num =0
完成任务数= 1 任务数= 1 活动线程数=0 线程数=1 最大线程数=1
task num =1
完成任务数= 2 任务数= 2 活动线程数=0 线程数=2 最大线程数=2
task num =2
完成任务数= 3 任务数= 3 活动线程数=0 线程数=3 最大线程数=3
task num =3
完成任务数= 4 任务数= 4 活动线程数=0 线程数=4 最大线程数=4
task num =4
java 线程池那点事儿的更多相关文章
- Java 线程池框架核心代码分析--转
原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...
- Java线程池使用说明
Java线程池使用说明 转自:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极 ...
- (转载)JAVA线程池管理
平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...
- Java线程池的那些事
熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...
- 四种Java线程池用法解析
本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...
- Java线程池的几种实现 及 常见问题讲解
工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能 ...
- Java线程池应用
Executors工具类用于创建Java线程池和定时器. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程.在任意点,在大多数 nThread ...
- Java线程池的原理及几类线程池的介绍
刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...
- Java线程池与java.util.concurrent
Java(Android)线程池 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行 ...
随机推荐
- Python连载38-协程、可迭代、迭代器、生产者消费者模型
一.生产者消费者模型 import multiprocessing from time import ctime def consumer(input_q): print("Into con ...
- Spring入门(十二):Spring MVC使用讲解
1. Spring MVC介绍 提到MVC,参与过Web应用程序开发的同学都很熟悉,它是展现层(也可以理解成直接展现给用户的那一层)开发的一种架构模式,M全称是Model,指的是数据模型,V全称是Vi ...
- http响应
1.http响应 2.响应行常见状态码 200 :请求成功. 302 :请求重定向. 当访问网址A时,由于网址A服务器端的拦截器或者其他后端代码处理的原因,会被重定向到网址B. 304 :请求资源没有 ...
- python安装第三方包的安装路径, dist-packages和site-packages区别
简单来说 如果是系统自带的python,会使用dist-packages目录 如果你手动安装python,它会直接使用目录site-packages 这允许你让两个安装隔离开来 dist-packag ...
- 03 (OC)* UITableView优化
一:cell注册和初始化 1:不注册cell 2:注册类 3:注册nib 4:storyboard 二:核心思想 1:UITableView的核心思想是:cell的重用机制.UITbleView只会创 ...
- 《Maven实战》读书笔记
一.Maven使用入门 POM(Project Object Model,项目对象模型),定义了项目的基本信息,用于描述项目如何构建,声明项目依赖等等 二.坐标和依赖 1.何为Maven坐标 Mave ...
- Eclipse中Spring Boot响应jsp的简单demo
首先在Eclipse里新建一个maven工程,这里的打包类型和父包如果后续再去pom中添加也可以 此时的工程路径是这样的 接下来去到pom中添加相关的依赖,如果有报错maven update一下即可 ...
- JS中3种风格的For循环有什么异同?
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者.原文出处:https://blog.bitsrc.io/3-flavors-of-the-for-loop-i ...
- SpringBoot起飞系列-日志使用(四)
一.SpringBoot中的日志组件 日志是一个系统中不可缺少的组件.在项目中,我们常用的日志组件有JUL.JCL.Jboss-logging.logback.log4j.log4j2.slf4j.. ...
- Python学习笔记整理总结【Django】:中间件、CSRF、缓存
一.中间件 中间件是一类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法:在django项目的settings模块中,有一个 MIDDLEWARE 变量,其中每 ...