版权声明:本文出自汪磊的博客,转载请务必注明出处。

Java线程池技术属于比较“古老”而又比较基础的技术了,本篇博客主要作用是个人技术梳理,没什么新玩意。

一、Java线程池技术的由来

我们平时使用线程来进行异步操作时,线程的创建,销毁等相对来说都是比较消耗资源的,试想这样一个业务情景:高并发请求,但是每次请求的时间非常短。如果我们为每一个请求都单独创建一个线程来执行,就会消耗大量设备资源,使设备处于高负荷状态,显然这样的处理就有很大问题了。这时候我们就可以用线程池技术来解决了,我们在线程池中创建若干条线程,当有任务需要执行时就从该线程池中获取一个线程来执行任务,如果一时间任务过多,超出线程池的线程数量,那么后面的线程任务就进入一个等待队列进行等待,直到线程池有线程处于空闲时才从等待队列获取要执行的任务进行处理,这样就减少了线程创建和销毁的开销,实现了线程的复用。

二、Executor框架介绍

首先对整体框架有个大概了解,如图:

Executor是个接口,就定义了一个void execute(Runnable command)方法。

ExecutorService同样是一个接口并且继承自Executor接口,对其方法进行扩展,其中最重要的是<T> Future<T> submit(Callable<T> task)方法,关于Callable,Future,FutureTask不是本篇重点,有时间会单独写一篇博客介绍。也可以自行搜索了解,比较简单。

AbstractExecutorService抽象类,实现了ExecutorService接口,主要实现了submit,ivokeAny方法。

ScheduledExecutorService同样是一个接口,继承自ExecutorService接口,对其进行扩展,主要就是schedule等方法。

ThreadPoolExecutor具体线程池实现类,继承自AbstractExecutorService抽象类,我们使用的时候大部分就是使用这个类,后面会具体讲到。

ScheduledThreadPoolExecutor 具有调度能力的线程池实现类,继承自ThreadPoolExecutor类,且实现ScheduledExecutorService接口,其主要功能就是调用schedule方法,可以延时或者周期的执行某一任务,而ThreadPoolExecutor是没有这一共能的。

三、ThreadPoolExecutor构造函数参数介绍

在我们使用ThreadPoolExecutor的时候会发现构造函数中有很多参数,如下:

 public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
} public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
} public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
} public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

前3个构造函数都是调用第4个构造函数,只不过有些参数使用默认的罢了。

接下来我们看下第4个构造函数每个参数意义:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,RejectedExecutionHandler handler)

参数 说明
corePoolSize 线程池中核心线程数量
maximumPoolSize 线程池中最大线程数量
keepAliveTime 非核心线程存活时间
unit keepAliveTime的时间单位
workQueue 存放任务的队列
threadFactory 用来生产线程的工厂
handler 当线程池中不能再放入任务时执行的handler

如果有一个corePoolSize为5,maximumPoolSize为10的线程池,可用下图形象展示:

这里要说明一下:所谓核心线程非核心线程只是一个数量的说明,并不是说核心线程非核心线程有本质上的不同,它们都是普通的线程而已,并且线程特性都一样,不是说核心线程有特殊标记,线程池能“认”出来这是核心线程,对其有特殊操作。

四、线程池处理任务的策略

我们调用线程池的submit()或execute()方法,向线程池中放入一个任务执行的时候线程池到底是怎么按照其策略来执行的呢?接下来,我们对其执行策略进行详细介绍,介绍完会对构造函数中每个参数有更深刻印象的。

1,调用线程池的submit()或execute()方法向线程池中放入一个任务,线程池内部会检查运行的线程数量是否达到corePoolSize数量,如果没有达到,则创建一个线程执行放入的任务,不管已经创建的线程是否处于空闲状态,创建线程的任务由threadFactory来完成,关于ThreadFactory可以参考我的另一篇博客来学习:java线程池技术(一):ThreadFactory与BlockingQueue。

2,我们继续向线程池中放入任务,此时线程池中运行的线程数量已经达到corePoolSize数量,则新加入的任务将会被放入workQueue中,直到有线程处于空闲状态,则从workQueue中取出任务执行。

3,继续向线程池中放入任务,此时线程池中运行的线程数量已经达到corePoolSize数量,并且workQueue中已经放满任务不能再放入新的任务,那么这时候就继续创建新的线程,注意此时线程池中线程数量已经多余corePoolSize数量,多出来的线程就叫做非核心线程。用非核心线程来执行新放入的任务。当线程池中的线程超过你设置的corePoolSize参数,说明当前线程池中有所谓的“非核心线程”。当某个线程处理完任务后,如果等待keepAliveTime时间后仍然没有新的任务分配给它,那么这个线程将会被回收。线程池回收线程时,对所谓的“核心线程”和“非核心线程”是一视同仁的,直到线程池中线程的数量等于你设置的corePoolSize参数时,回收过程才会停止。

4, 继续向线程池中放入任务,此时线程池中运行的线程数量已经达到maximumPoolSize数量,并且workQueue中已经放满任务不能再放入新的任务,由于线程池中运行的线程

已经达到maximumPoolSize数量,所以无法再创建线程执行新放入的任务,此时handler参数就起作用了,在使用的时候相信大部分开发者都没用过这个参数,我们看下系统默认怎么处理的,

系统默认闯入传入的是defaultHandler,如下:

 public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}

初始化如下:

     private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();

接下来看下AbortPolicy这个类吧:

     public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { } /**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}

AbortPolicy实现了RejectedExecutionHandler接口,在rejectedExecution方法中抛出RejectedExecutionException异常。

所以如果线程池中运行的线程数量已经达到maximumPoolSize数量,并且workQueueworkQueue中已经放满任务不能再放入新的任务,系统默认情况下就会抛出

RejectedExecutionException异常,我们也可以自己实现RejectedExecutionHandler接口,在rejectedExecution方法中实现自己策略。比如我自己写的网络请求框架就自己定义了

RejectedExecutionHandler,如下:

 public class RejectedPolicy implements RejectedExecutionHandler{

     @Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
taskQuene.put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

好了,以上就是线程池具体执行一个新任务的大体策略,是不是有了更深的认识???

以上分析中涉及的ThreadFactory与BlockingQueue如果你不是太了解,可以参考我的另一篇博客了解一下:java线程池技术(一):ThreadFactory与BlockingQueue。

好了,关于线程池大体介绍就到此为止,希望对你有用。

java线程池技术(二): 核心ThreadPoolExecutor介绍的更多相关文章

  1. Java 线程池(二)

    简介 在上篇 Java 线程池(一) 我们介绍了线程池中一些的重要参数和具体含义,这篇我们看一看在 Java 中是如何去实现线程池的,要想用好线程池,只知其然是远远不够的,我们需要深入实现源码去了解线 ...

  2. Java线程池中的核心线程是如何被重复利用的?

    真的!讲得太清楚了!https://blog.csdn.net/MingHuang2017/article/details/79571529 真的是解惑了 本文所说的"核心线程". ...

  3. juc线程池原理(二):ThreadPoolExecutor的成员变量介绍

    概要 线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析ThreadPoolExecutor类,来了解线程池的原理. ThreadPoolExecutor数据结构 Thread ...

  4. java线程池技术

    1.线程池的实现原理?简介: 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力.假设一个服务器完成一项任务所需时间为:T1 创建线程时间, ...

  5. 线程池技术之:ThreadPoolExecutor 源码解析

    java中的所说的线程池,一般都是围绕着 ThreadPoolExecutor 来展开的.其他的实现基本都是基于它,或者模仿它的.所以只要理解 ThreadPoolExecutor, 就相当于完全理解 ...

  6. Java线程池技术以及实现

    对于服务端而言,经常面对的是客户端传入的短小任务,需要服务端快速处理并返回结果.如果服务端每次接受一个客户端请求都创建一个线程然后处理请求返回数据,这在请求客户端数量少的阶段看起来是一个不错的选择,但 ...

  7. 线程池系列二:ThreadPoolExecutor讲解

    一.简介 1)线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为: ThreadPoolExecutor(int corePoolSize, i ...

  8. java线程池技术(一):ThreadFactory与BlockingQueue

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.ThreadFactory概述以及源码分析 ThreadFactory很简单,就是一个线程工厂也就是负责生产线程的,我们看下ThreadFact ...

  9. java线程池系列(1)-ThreadPoolExecutor实现原理

    前言 做java开发的,一般都避免不了要面对java线程池技术,像tomcat之类的容器天然就支持多线程. 即使是做偏后端技术,如处理一些消息,执行一些计算任务,也经常需要用到线程池技术. 鉴于线程池 ...

随机推荐

  1. scala 小结(一)

    Scala 是什么?(What is scala?)   引用百度百科对于scala的定义: Scala是一门多范式的编程语言,一种类似java的编程语言,设计初衷是实现可伸缩的语言.并集成面向对象编 ...

  2. 计算机中RAM和ROM

    1.RAM(RamdomAccessMemory): 易挥发性随机存取存储器,高速存取,读写时间相等,且与地址无关,如计算机内存等. 2.ROM(Read Only Memory): 只读存储器.断电 ...

  3. spring之AspectJ基于注解 AOP编程

    一.前言 使用注解代替之前在spring配置文件中配置目标类.切面类和aop配置. 二.注意 需要注意的是,需要在spring配置文件中引入如下,如果不添加,切面类中的@Aspect注解将不起作用 & ...

  4. 浏览器中页面的visibility状态及变化监听

    需求 在浏览器中播放视频,当用户进行页面切换操作时.需要根据视频播放页是否处于可见状态,来控制视频的暂停及重新播放. 相关文档 参考MDN中,关于页面的可见性相关的API说明.https://deve ...

  5. js 面向对象 继承

    继承方式有四种: 1.call 2.apply 3.prototype 4.for in call 和 apply 的主要区别: call 传参数只能一个一个的传, apply 因为是用数组,所以可以 ...

  6. HDU [P1151] Air Raid

    二分图匹配求DAG图上的最小路径覆盖 应用了拆点的思想,将DAG图上的每一个点拆成二分图的x集合与y集合,对于一条有向边u->v来说,我们在ux与vy之间连一条边,然后求二分图的最大匹配 DAG ...

  7. BZOJ 1004: [HNOI2008]Cards [Polya 生成函数DP]

    传送门 题意:三种颜色,规定使用每种颜色次数$r,g,b$,给出一个置换群,求多少种不等价着色 $m \le 60,\ r,g,b \le 20$ 咦,规定次数? <组合数学>上不是有生成 ...

  8. 浅析Xilinx 三速以太网MAC IP核

    之前在使用Altera的三速以太网MAC IP的基础上,完成了UDP协议数据传输.此次为了将设计移植到xilinx FPGA上,需要用到xilinx的三速以太网MAC IP核,当然也可以自己用HDL编 ...

  9. centos 6 安装zabbix 3.0

    1.安装PHP Zabbix 3.0对PHP的要求最低为5.4,而CentOS6默认为5.3.3,完全不满足要求,故需要利用第三方源,将PHP升级到5.4以上,注意,不支持PHP7 rpm -ivh  ...

  10. 【NOIP2012】 疫情控制

    [NOIP2012] 疫情控制 标签: 倍增 贪心 二分答案 NOIP Description H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树, 1 号城市是首都, 也是 ...