当系统系统规模较小,我们可以不使用线程池。但是当系统到达一定规模,频繁的创建和销毁线程池会消耗很多资源。

合理利用线程池能够带来三个好处。

1降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

2提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

3提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

下面演示下线程池的基本的使用

public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()); Runnable runnable = null;
for (int i = 0; i < 20; i++) {
runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " run");
}
};
executor.execute(runnable);
}
executor.shutdown();
}
}

  

Jdk默认了实现了4种线程池。
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,默认是60秒,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行

查看源码 ,都是调用

其本质是通过不同的参数初始化一个ThreadPoolExecutor对象,只是传递的参数不同罢了

具体参数解释如下:

corePoolSize 线程池中的核心线程数,

maximumPoolSize 线程池中允许的最大线程数

keepAliveTime 线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间

keepAliveTime的 单位 unit

workQueue

用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序任务;
LinkedBlockingQuene:基于链表的无界阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
priorityBlockingQuene:具有优先级的无界阻塞队列;

handler

线程池的拒绝策略,当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池内置了4种策略:
1、AbortPolicy:直接抛出异常,默认策略;
2、CallerRunsPolicy:用调用者所在的线程来执行任务;
3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
4、DiscardPolicy:直接丢弃任务;
当然我们也可以根据应用场景实现RejectedExecutionHandler接口,自定义拒绝策略,

实际开发中,一般先记录日志 再开一个定时去处理

执行流程图如下

======================================================================

原理解析 

找到线程池核心的execute()方法 源代码如下

 if (command == null)
throw new NullPointerException();
//ctl是一个包装变量 包含了线程池的状态以及线程池中线程数
int c = ctl.get();
//workerCountOf 获取线程池的当前线程数
if (workerCountOf(c) < corePoolSize) { // 【步骤1】
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { // 【步骤2】
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //【步骤4】
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false)) // 【步骤3】
reject(command);
}

  

具体的执行流程如下:
--> 如果线程数小于corePoolSize,则执行addWorker方法创建新的线程执行任务 并返回 ;否则执行步骤2;
--> 如果线程池处于RUNNING状态,并且任务成功放入阻塞队列中,则执行步骤4,否则执行 步骤3
--> 再次检查线程池的状态,如果线程池没有RUNNING,并且从阻塞队列中删除任务,则执行reject方法处理任务;
--> 执行addWorker方法创建新的线程执行任务,如果addWoker执行失败,则执行reject方法处理任务;

接下来我们看看addWorker方法

addWorker分为2部分

retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false; for (;;) {
int wc = workerCountOf(c);
//通过自旋的方式,判断要添加的Worker是否是true,如果是的话,那么
//则判断当前的workerCount是否大于corePoolsize,否则则判断是否大于//maximumPoolSize,如果满足的话,说明workerCount超出了线程池大小,直//接返回false 如果没有超出就cas让workerCount+1 如果成功就跳出循环 失//败就继续进行状态的判断
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
--------第一部分完
/*如果满足了的话,那么则创建一个新的Worker对象 满足状态就添加到works中 然后启动Worker中的线程开始执行任务*/
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

  

java多线程系列16 线程池的更多相关文章

  1. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  2. Java多线程系列--“JUC线程池”02之 线程池原理(一)

    概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...

  3. Java多线程系列--“JUC线程池”03之 线程池原理(二)

    概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...

  4. Java多线程系列--“JUC线程池”04之 线程池原理(三)

    转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...

  5. Java多线程系列--“JUC线程池”05之 线程池原理(四)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

  6. Java多线程系列--“JUC线程池”01之 线程池架构

    概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容——线程池.内容包括:线程池架构 ...

  7. java多线程系列(六)---线程池原理及其使用

    线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...

  8. Java多线程系列 JUC线程池04 线程池原理解析(三)

    转载 http://www.cnblogs.com/skywang12345/p/3509954.html  https://blog.csdn.net/qq_22929803/article/det ...

  9. Java多线程系列 JUC线程池06 线程池原理解析(五)

    ScheduledThreadPoolExecutor解析 ScheduledThreadPoolExecutor适用于延时执行,或者周期性执行的任务调度,ScheduledThreadPoolExe ...

随机推荐

  1. php 7 event 安装

    有效安排I/O,时间和信号的扩展 使用可用于特定平台的最佳I/O通知机制的事件,是PHP基础设施的libevent端口. 下载地址:http://pecl.php.net/package/event ...

  2. PAT 乙级 1092 最好吃的月饼 (20 分)

    1092 最好吃的月饼 (20 分) 月饼是久负盛名的中国传统糕点之一,自唐朝以来,已经发展出几百品种. 若想评比出一种“最好吃”的月饼,那势必在吃货界引发一场腥风血雨…… 在这里我们用数字说话,给出 ...

  3. CSS之img标签

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. [UE4]Invalidation Box

    Invalidation Box:使条目无效的容器.使容器内的条目不再更新,如果确定某一个UI不需要更新的话,就可以把这个UI放到Invalidation Box中. 一.Invalidation B ...

  5. matplotlib绘图总结《转》

    本文作为学习过程中对matplotlib一些常用知识点的整理,方便查找. 类MATLAB API 最简单的入门是从类 MATLAB API 开始,它被设计成兼容 MATLAB 绘图函数. from p ...

  6. element-vue-koa2-mysql实现文件上传

    友情提示:这篇博客不会详细说明搭建过程 阅读群体建议:第一次使用node或者koa2写文件上传或者下载,因为你不知道用fs的哪个方法,我也是从fs里试水试了一天,各种百度才搞出来的,特别学过java的 ...

  7. Mybatis调用数据库的存储过程和方法

     转载. https://blog.csdn.net/ml0228123/article/details/81002258   上次的项目,要求我用java代码调用存储过程,折腾了好久.最后总算成功了 ...

  8. 单链表查找第i个节点

    #include<stdio.h> #include<string.h> #include<stdlib.h> typedef struct Node { char ...

  9. @Bean 的用法

    @Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里.添加的bean的id为方法名 定义bean 下面是@Configuratio ...

  10. 一篇面经(BAT面试)(转)

    0. 写在之前 首先呢我的面试经历和一些面霸和收割机的大神相比绝不算丰富,但我这三个月应该能代表很大一部分人的心路历程:从无忧无虑也无知的状态,然后遭遇挫败,跌入低谷,连续数天的黑暗,慢慢调整,逼着自 ...