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 线程池参数
  1. corePoolSize 核心线程数。默认情况下,当提交一个任务时线程池会新建一个线程池(及时有空闲线程存在),直到线程数量等于基本大小(也可以预初始化线程)

  2. workQueue 一个存放任务的阻塞队列,当线程池里的基本线程都在忙着的时候,提交一个新任务的话会暂时存放到阻塞队列,等待执行,常用的阻塞队列有一下几种

    • ArrayBlockingQueue 基于数组实现的FIFO阻塞队列(有限长度)
    • LinkedBlockingQueue 基于链表实现的FIFO阻塞队列(无限长度)
    • SynchronousQueue 本身不存储任务,当有任务来的时候会一直阻塞,直到线程去执行
    • PriorityBlockingQueue 具有优先级的优先队列
  3. maximumPoolSize 最大线程数。当所有的核心线程都在运行并且阻塞队列也满了的话会创建额外的几个线程来执行,这时候线程池里的所有线程数量就是最大线程数量。如果线程池采用的是像LinkedBlockingQueue这种无界队列的话,该参数不会起到作用

  4. KeepAliveTimeTimeUnit 如果某个线程的空闲时间大于KeepAliveTime的时候会被标记为可回收, 并且当前线程池里的数量大于核心线程数量的时候会被终止。所以该参数跟maximumPoolSize一样,使用无界队列的时候不起作用,TimeUnit是时间单位

  5. RejectedExecusionHandler 饱和策略。如果我们的任务队列满了,并且线程池里的线程数量已经达到了最大线程数,而且这些线程都不再空闲状态。这时候新提交任务的话,无法去执行,所以需要一种饱和策略来去处理这些任务。Java提供了一下几种策略

    • AbortPolicy (默认策略) 直接抛异常,调用者需要根据自己的需求去捕获处理异常
    • DiscardPolicy 直接丢弃,不处理直接丢弃该任务
    • DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务。如果是优先队列的话会丢弃优先级最高的任务,所以不推荐与优先队列一起组合使用
    • CallerRunsPolicy 由调用者当前线程来执行该任务。
2.3 常见线程池

Java类库提供了灵活的创建线程池方法,可以通过调用Executors中的静态工厂方法来创建一个线程池。

  1. newFixedThreadPool 将创建一个固定线程数量的线程池,每当提交一个任务时就创建一个线程。直到达到线程池的最大数量。corePoolSize和maximumPoolSize值相同,并且采用了LinkedBlockingQueue,所以最大线程池数,饱和策略,存活时间等等参数都将不被用到。
   public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
  1. newSingleThreadExecutor 一个但线程的Executor,它跟上面的newFixedThreadPool类似,区别在于只有一个工作线程。通过该线程池可以确保有序的执行队列中的任务,因为只有一个工作线程,所以出队的顺序就是执行的顺序。
    public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
  1. newCachedThreadPool 此线程池的线程数量没有显式的限制默认为Integer.MAX_VALUE,可以为每个任务创建一个线程来处理,但是它没这么做,它是具有缓存线程的功能,是一种可伸缩的。比如,当处理新任务是首先看有没有空闲可用的线程来处理,如果没有的话才会新创建。并且当创建的线程空闲一段时间之后会回收(默认一分钟)
    public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
  1. 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 线程池那点事儿的更多相关文章

  1. Java 线程池框架核心代码分析--转

    原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...

  2. Java线程池使用说明

    Java线程池使用说明 转自:http://blog.csdn.net/sd0902/article/details/8395677 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极 ...

  3. (转载)JAVA线程池管理

    平时的开发中线程是个少不了的东西,比如tomcat里的servlet就是线程,没有线程我们如何提供多用户访问呢?不过很多刚开始接触线程的开发攻城师却在这个上面吃了不少苦头.怎么做一套简便的线程开发模式 ...

  4. Java线程池的那些事

    熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...

  5. 四种Java线程池用法解析

    本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 执行一个异步任务你还只是如下 ...

  6. Java线程池的几种实现 及 常见问题讲解

    工作中,经常会涉及到线程.比如有些任务,经常会交与线程去异步执行.抑或服务端程序为每个请求单独建立一个线程处理任务.线程之外的,比如我们用的数据库连接.这些创建销毁或者打开关闭的操作,非常影响系统性能 ...

  7. Java线程池应用

    Executors工具类用于创建Java线程池和定时器. newFixedThreadPool:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程.在任意点,在大多数 nThread ...

  8. Java线程池的原理及几类线程池的介绍

    刚刚研究了一下线程池,如果有不足之处,请大家不吝赐教,大家共同学习.共同交流. 在什么情况下使用线程池? 单个任务处理的时间比较短 将需处理的任务的数量大 使用线程池的好处: 减少在创建和销毁线程上所 ...

  9. Java线程池与java.util.concurrent

    Java(Android)线程池 介绍new Thread的弊端及Java四种线程池的使用,对Android同样适用.本文是基础篇,后面会分享下线程池一些高级功能. 1.new Thread的弊端执行 ...

随机推荐

  1. 装系统------- 了解常用的启动方式以及如何进入bios

    1.从硬盘启动:这种方式提供了最简单的维护解决方案,其基本原理就是增加一个系统的开机启动项,每次开机的时候您都可以选择是进入本地系统还是进入PE. 安装程序并不将PE的启动项作为默认启动项,而是提供一 ...

  2. http响应

    1.http响应 2.响应行常见状态码 200 :请求成功. 302 :请求重定向. 当访问网址A时,由于网址A服务器端的拦截器或者其他后端代码处理的原因,会被重定向到网址B. 304 :请求资源没有 ...

  3. 简说Python发展及其就业前景

    简说python 发展历史 Python是著名的"龟叔"Guido van Rossum在1989年圣诞节期间,为了打发无聊的圣诞节而编写的一个编程语言. python从ABC语言 ...

  4. DirectX12 3D 游戏开发与实战第一章内容

    DirectX12 3D 第一章内容 学习目标 1.学习向量在几何学和数学中的表示方法 2.了解向量的运算定义以及它在几何学中的应用 3.熟悉DirectXMath库中与向量有关的类和方法 1.1 向 ...

  5. Windows导出文件夹中的文件名列表

    在需要导出的目录中,shift+右键,打开cmd或者powershell 运行命令:dir -name >list.txt 刷新文件夹,打开list.txt

  6. 判断java中最多的词组

    其中的难点,是空格,以及如果第一个是空格怎么办,虽然事后看着很简单,但是做的时候却十分的困难! static void Daunyu()throws IOException {     Word wo ...

  7. mysql修改数据库的存储引擎(InnoDB)

    查看当前的存储引擎 show engines; 基本的差别:MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持.MyISAM类型的表强调的是性能,其执行数度比InnoDB类型更快,但是不 ...

  8. 死磕 java同步系列之Phaser源码解析

    问题 (1)Phaser是什么? (2)Phaser具有哪些特性? (3)Phaser相对于CyclicBarrier和CountDownLatch的优势? 简介 Phaser,翻译为阶段,它适用于这 ...

  9. HTML学习之轮播图

    这可以说是一种非常简单的实现轮播图的方法了,由于时间仓促所以没写自动轮播部分.简单说一下原理吧,就是把所有图片堆叠在一个盒子里,设置所有图片的透明度为0,这样就把所有图片都隐藏了,第一张图片除外(第一 ...

  10. python爬虫添加请求头和请求主体

    添加头部信息有两种方法 1.通过添加urllib.request.Request中的headers参数 #先把要用到的信息放到一个字典中 headers = {} headers['User-Agen ...