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

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. Node-debug方法

    本文使用配置node-inspector配合chorme完成debug(编辑器使用SublimeText3). 1.用命令行进入安装node的目录,使用npm install -g node-insp ...

  2. Apache中的gzip压缩作用及配置

    gzip会对文本资源进行压缩,一般能节省40%的大小,二进制内容不需要开启Gzip压缩,因为这些文件是已经压缩过的,如果再进行gzip压缩可能反而会增加其大小,并且空耗cpu资源啊. 静态资源一般都会 ...

  3. Java I/O基础

    字节流和字符流的区别,字节流一次读取一个字节,字符流一次读取的是一个Unicode码,读取了2个字节. 可以以文本编辑器打开的可以使用字符流读取,否则用字符流读取可能就会出错.图像文件就需要用字节流读 ...

  4. BZOJ 1770: [Usaco2009 Nov]lights 燈 [高斯消元XOR 搜索]

    题意: 经典灯问题,求最少次数 本题数据不水,必须要暴搜自由元的取值啦 想了好久 然而我看到网上的程序都没有用记录now的做法,那样做遇到自由元应该可能会丢解吧...? 我的做法是把自由元保存下来,枚 ...

  5. SPOJ 7258 Lexicographical Substring Search [后缀自动机 DP]

    题意:给一个长度不超过90000的串S,每次询问它的所有不同子串中,字典序第K小的,询问不超过500个. 第一道自己做的1A的SAM啦啦啦 很简单,建SAM后跑kth就行了 也需要按val基数排序倒着 ...

  6. webrtc底层一对一连接过程探索(三)

    一.连接过程继续解读-----fun33-fun35解读 完整代码如下: //fun33-37 console.error('fun35-37==>2332==>2332'); var q ...

  7. Windows Server 2016-FSMO操作主机角色介绍

    FSMO五个操作主机角色 1.林范围操作主机角色(两种): 架构主机角色:Schema Master 域命名主机角色:Domain Naming Master 2.域范围操作主机角色(三种): 域范围 ...

  8. python进行各类API的使用

    前言: 献上歌曲一首: 因为快要上学了,昨天晚上熬夜.然后今天早上起床 没有什么精神.吃完午饭后开始思考今天写什么好呢 然后逛着逛着逛到了一个API网站.感觉还不错就爬了 0x01: 环境:windo ...

  9. typedef struct 的用法

    typedef struct stu { int age; char name[20]; }stu,*pstu; stu stu1;相当于struct stu stu1; pstu pstu1;相当于 ...

  10. 如何在Centos 7上用Logrotate管理日志文件

    何为Logrotate? Logrotate是一个实用的日志管理工具,旨在简化对系统上生成大量的日志文件进行管理. Logrotate允许自动旋转压缩,删除和邮寄日志文件,从而节省宝贵的磁盘空间. L ...