背景

公司业务性能优化,使用java自带的Executors.newFixedThreadPool()方法生成线程池。但是其内部定义的LinkedBlockingQueue容量是Integer.MAX_VALUE。考虑到如果数据库中待处理数据量很大有可能会在短时间内往LinkedBlockingQueue中填充很多数据,导致内存溢出。于是看了一下线程池这块的源码,并在此记录。

类图

  • Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的;

  • ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等

  • 抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法;submit() 方法

  • ThreadPoolExecutor继承了类AbstractExecutorService。实现了execute(Runnable)方法。

  • Executors提供的集中工厂方法都是调用的ThreadPoolExecutor的构造方法。因为这个构造方法参数比较多 所以提供了几个经典的实现。

ExecutorService newCachedThreadPool = Executors.newFixedThreadPool();
ExecutorService newCachedThreadPool = Executors.newSingleThreadExecutor();
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newCachedThreadPool = Executors.newScheduledThreadPool();
  • 本篇违章主要包括以下几点内容。这也是解决背景中提到的问题的主要历程。

    1.ThreadPoolExecutor构造方法

    2.ExecutorService submit() 方法的实现

    2.Executor execute() 方法的实现

    3.reject() 拒绝策略

ThreadPoolExecutor构造方法

构造方法中赋值的成员标量:

// 构造方法中用到的成员变量
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
private volatile long keepAliveTime; //线程空闲之后存货时间 (线程数量大于corePoolSize之后)
private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程 
private volatile RejectedExecutionHandler handler; //任务拒绝策略

通过代码可以知道 Executors提供的集中工厂方法实际都是调用的同一个ThreadPoolExecutor的构造方法。当然我们也可以通过自己调用ThreadPoolExecutor构造方法 自己设置参数 从而获得很贴合我们业务的线程池。

AbstractExecutorService submit() 方法

/**
* @throws RejectedExecutionException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
*/
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}

其实是调用了execute() 方法,execute()方法 由ThreadPoolExecutor类实现。

ThreadPoolExecutor execute()方法

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 29位
private static final int COUNT_BITS = Integer.SIZE - 3;
// 0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY = (1 << COUNT_BITS) - 1; // runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl
// 高三位 代表 状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
// 低三位 代表 数量
private static int workerCountOf(int c) { return c & CAPACITY; }
// 把状态和数量两个值 揉在一起
// private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 获取到当前有效的线程数和线程池的状态
int c = ctl.get();
// 1.获取当前正在运行线程数是否小于核心线程池,是则新创建一个线程执行任务,否则将任务放到任务队列中
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2.当前核心线程池中全部线程都在运行workerCountOf(c) >= corePoolSize,所以此时将线程放到任务队列中
// 线程池是否处于运行状态,且是否任务插入任务队列成功。注意这块 && 是做了优化如果前面条件失败后面语句不会处理
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);
}
// 3.插入队列不成功 offer() 方法失败是因为队列满了,此时就新创建线程去执行任务,创建失败抛出异常
else if (!addWorker(command, false))
reject(command);
}
// CAS修改clt的值+1,成功退出cas循环,失败继续
if (compareAndIncrementWorkerCount(c))
break retry;
//将新建的线程加入到线程池中
workers.add(w);
int s = workers.size();
//修正largestPoolSize的值
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;

addWorker()方法 总结起来就两部分

1.CAS+失败重试操作来将线程数加1

2.新建一个线程并启用。

RejectedExecutionHandler拒绝策略

java 内置的四种拒绝策略。

 public static class AbortPolicy implements RejectedExecutionHandler  // 抛出java.util.concurrent.RejectedExecutionException异常
public static class CallerRunsPolicy implements RejectedExecutionHandler //直接在 execute 方法的调用线程中运行被拒绝的任务。如果执行程序已关闭,则会丢弃该任务
public static class DiscardPolicy implements RejectedExecutionHandler // 不做任何处理 直接丢弃
public static class DiscardOldestPolicy implements RejectedExecutionHandler // 丢弃老的

自定义拒绝策略:

new RejectedExecutionHandler() {
// 自定义拒绝策略
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// 如果LinkedBlockingQueue存满了,阻塞等待有空间后再加入元素。(put方法是阻塞的)
LOGGER.info("LinkedBlockingQueue has been full ");
// put() 方法是阻塞的,如果队列没有空间会一直等待。
executor.getQueue().put(r);
LOGGER.info("thread has been put in"); } catch (InterruptedException e) {
e.printStackTrace();
}
}

总结一点:当用java内置的一些工具的时候,如果有不理解的一定要 深入去看源码。从根本上找解决思路。

java内置线程池ThreadPoolExecutor源码学习记录的更多相关文章

  1. Java并发之线程池ThreadPoolExecutor源码分析学习

    线程池学习 以下所有内容以及源码分析都是基于JDK1.8的,请知悉. 我写博客就真的比较没有顺序了,这可能跟我的学习方式有关,我自己也觉得这样挺不好的,但是没办法说服自己去改变,所以也只能这样想到什么 ...

  2. Java核心复习——线程池ThreadPoolExecutor源码分析

    一.线程池的介绍 线程池一种性能优化的重要手段.优化点在于创建线程和销毁线程会带来资源和时间上的消耗,而且线程池可以对线程进行管理,则可以减少这种损耗. 使用线程池的好处如下: 降低资源的消耗 提高响 ...

  3. Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析

    目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...

  4. 【Java并发编程】21、线程池ThreadPoolExecutor源码解析

    一.前言 JUC这部分还有线程池这一块没有分析,需要抓紧时间分析,下面开始ThreadPoolExecutor,其是线程池的基础,分析完了这个类会简化之后的分析,线程池可以解决两个不同问题:由于减少了 ...

  5. 为什么阿里Java规约禁止使用Java内置线程池?

    IDEA导入阿里规约插件,当你这样写代码时,插件就会自动监测出来,并给你红线提醒. 告诉你手动创建线程池,效果会更好. 在探秘原因之前我们要先了解一下线程池 ThreadPoolExecutor 都有 ...

  6. Python线程池ThreadPoolExecutor源码分析

    在学习concurrent库时遇到了一些问题,后来搞清楚了,这里记录一下 先看个例子: import time from concurrent.futures import ThreadPoolExe ...

  7. 线程池ThreadPoolExecutor源码分析

    在阿里编程规约中关于线程池强制了两点,如下: [强制]线程资源必须通过线程池提供,不允许在应用中自行显式创建线程.说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源 ...

  8. 线程池ThreadPoolExecutor源码解读研究(JDK1.8)

    一.什么是线程池 为什么要使用线程池?在多线程并发开发中,线程的数量较多,且每个线程执行一定的时间后就结束了,下一个线程任务到来还需要重新创建线程,这样线程数量特别庞大的时候,频繁的创建线程和销毁线程 ...

  9. 浅析线程池 ThreadPoolExecutor 源码

    首先看下类的继承关系,不多介绍: public interface Executor {void execute(Runnable);} public interface ExecutorServic ...

随机推荐

  1. XGBoost原理和公式推导

     本篇文章主要介绍下Xgboost算法的原理和公式推导.关于XGB的一些应用场景在此就不赘述了,感兴趣的同学可以自行google.下面开始: 1.模型构建 构建最优模型的方法一般是最小化训练数据的损失 ...

  2. node简介

    1. Node.js是什么? 简单的说 Node.js 就是运行在服务端的 JavaScript. Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台. Node.js ...

  3. Go语言基础之结构体

    Go语言基础之结构体 Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念.Go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性. 类型别名和自定义类型 自定义类型 在G ...

  4. win10 vscode使用 智能提示

    1.没有第三方库的智能提示 参考:https://code.visualstudio.com/docs/python/editing 1.点开Settings 2.搜索加添加 3.添加后的内容 然后就 ...

  5. JAVA---MYSQL 基本知识点 第一部分

     一 :  什么是数据库? 数据库就是数据的仓库,用来存取数据的,也是一个文件系统,但是访问这个这个文件需要通过标准的SQL语句(统一的规则), 二 : 什么是关系型数据库?  关系型数据库通常保存的 ...

  6. Ubuntu部署python3.7的开发和运行环境

    Ubuntu部署python3.7的开发和运行环境 1 概述 由于最近项目全部由python2.x转向 python3.x(使用 python3.7.1) ,之前的云主机的的默认python版本都面临 ...

  7. rem布局完成响应式开发,通俗且详细的原理解析和代码实现

    一.rem布局基本原理 原理:rem可以理解为一个长度单位,单位rem的值等于网页font-size的值.如果网页的字体大小为默认值16px,那么1rem就等于16px,0.5rem等于8px. 根据 ...

  8. MyCat-schema.xml详解

    一.概念与图示 schema.xml配置的几个术语与其关系图示: 二.schema 标签 schema 标签用于定义 MyCat 实例中的逻辑库,如: <schema name="US ...

  9. [Swift]LeetCode783. 二叉搜索树结点最小距离 | Minimum Distance Between BST Nodes

    Given a Binary Search Tree (BST) with the root node root, return the minimum difference between the ...

  10. 【mysql】Date和String的互相转换(DATE_FORMAT & STR_TO_DATE)

    1.Date  ——>  String 使用的函数:DATE_FORMAT(date,format)     date:需要转换的日期       format:格式化的样式 format样式整 ...