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. DirectX12 3D 游戏开发与实战第三章内容

    变换 学习目标 理解如何使用矩阵表示线性变换和仿射变换 学习对几何体进行缩放.旋转和平移的坐标变换 根据矩阵之间的乘法运算性质,将多个变换矩阵合并为一个单独的净变换矩阵 找寻不同坐标系之间的坐标转换方 ...

  2. 02 jvm简介

    声明:本博客仅仅是一个初学者的学习记录.心得总结,其中肯定有许多错误,不具有参考价值,欢迎大佬指正,谢谢!想和我交流.一起学习.一起进步的朋友可以加我微信Liu__66666666 这是简单学习一遍之 ...

  3. 控制执行流程之ForEach语法

    1.定义 : 一种更加简洁的for语法,用于数组和容器===>>>foreach,无需创建int类型变量对被访问项构建的序列进行计数. 2.经典应用:  对于数组的初始化,如果选用f ...

  4. Properties -IO相关的双列集合类

    IO相关的集合类 java.util.Properties集合 extends hashtable(淘汰) Properties类表示了一个持久的属性集.Properties可保存流中或从流中加载 P ...

  5. 2018年蓝桥杯java b组第六题

    标题:递增三元组 给定三个整数数组A = [A1, A2, ... AN], B = [B1, B2, ... BN], C = [C1, C2, ... CN],请你统计有多少个三元组(i, j, ...

  6. Autofac的AOP面向切面编程研究

    *:first-child { margin-top: 0 !important; } .markdown-body>*:last-child { margin-bottom: 0 !impor ...

  7. 报表统计——java实现查询某年12个月数据,没数据补0

    一般图表绘制例如echarts等,返回数据格式都大同小异.重点是利用sql或者java实现数据格式的转型,接下来是关键部分: 1.mapper层sql语句,返回统计好的月份与对应月份的数据. < ...

  8. 正睿OI DAY3 杂题选讲

    正睿OI DAY3 杂题选讲 CodeChef MSTONES n个点,可以构造7条直线使得每个点都在直线上,找到一条直线使得上面的点最多 随机化算法,check到答案的概率为\(1/49\) \(n ...

  9. Docker系列(五):.Net Core实现k8s健康探测机制

    k8s通过liveness来探测微服务的存活性,判断什么时候该重启容器实现自愈.比如访问 Web 服务器时显示 500 内部错误,可能是系统超载,也可能是资源死锁,此时 httpd 进程并没有异常退出 ...

  10. python process

    原文:https://www.cnblogs.com/LY-C/p/9145729.html 使用process模块可以创建进程 from multiprocessing import Process ...