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. ios 各个手机型号尺寸及分辨率适配

    常用宏 // 判断是否是ipad #define isPad ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdio ...

  2. django模型层之多表关系

    一. 多表操作 数据库表关系之关联字段与外键约束 一对多 book(多) publish(一) 查询<<水浒传>>这本书出版社的地址: select publish_id fr ...

  3. SQL手工注入进阶篇

    0.前言 上一篇我们介绍了SQL手工注入的流程以及步骤,但在实际的安全问题以及CTF题目中,查询语句多种多样,而且是肯定会对用户的输入进行一个安全过滤的,而这些过滤并不一定是百分百安全的,如何利用一些 ...

  4. 搭建数据库galera集群

    galera集群 galera简介 galera集群又叫多主集群,用于数据库的同步,保证数据安全 最少3台,最好是奇数台数,当一台机器宕掉时,因为仲裁机制,这台机器就会被踢出集群. 通过wsrep协议 ...

  5. Spring Boot中@ConfigurationProperties注解实现原理源码解析

    0. 开源项目推荐 Pepper Metrics是我与同事开发的一个开源工具(https://github.com/zrbcool/pepper-metrics),其通过收集jedis/mybatis ...

  6. 使用file_get_contents() 发送GET、POST请求

    服务器端执行HTTP请求,大家经常使用的就是CURL,curl工具的确是很好的数据文件传输工具,那么除此之外还有其他的工具能实现这个功能吗? 现在为你介绍一个很常见的工具 file_get_conte ...

  7. 孙悟空的七十二变是那般?--java类型的七十二变揭秘

    故事背景 在<西游记>原著第六回,孙悟空大闹天宫反下界,玉帝派十万天兵围剿,却被打得落花流水.玉帝不得不放下架子,请自己外甥二郎神回来支援.孙悟空与二郎神本事差不多,两人斗得不分胜负,但二 ...

  8. CPU的物理数、核心数、线程数

    最近了解下CPU的参数,主要是对常见的CPU参数指标:物理数.核心数以及线程数做了下了解.增长了点自己的见识,方便自己回忆和分享,记录下来.参考了网上的一些说明并加以整理,形成该随笔.主要参考链接如下 ...

  9. SpringBootSecurity学习(17)前后端分离版之 OAuth2.0 数据库(JDBC)存储客户端

    自动批准授权码 前面我们授权的流程中,第一步获取授权码的时候,都会经历一个授权是否同意页面: 这个流程就像第三方登录成功后,提问是否允许获取昵称和头像信息的页面一样,这个过程其实是可以自动同意的,需要 ...

  10. TreeMap剖析

    TreeMap实现有序要么就是外界传递进来Comparator对象,要么就使用默认key的Comparable接口(实现自然排序) 最后我就来总结一下TreeMap要点吧: 由于底层是红黑树,那么时间 ...