1. 为什么要用线程池

  • 减少资源的开销
  • 减少了每次创建线程、销毁线程的开销。
  • 提高响应速度 ,每次请求到来时,由于线程的创建已经完成,故可以直接执行任务,因此提高了响应速度。
  • 提高线程的可管理性 ,线程是一种稀缺资源,若不加以限制,不仅会占用大量资源,而且会影响系统的稳定性。 因此,线程池可以对线程的创建与停止、线程数量等等因素加以控制,使得线程在一种可控的范围内运行,不仅能保证系统稳定运行,而且方便性能调优。

2. 类图

  ExecutorService提供了两种基础线程池的选择,ScheduledThreadPoolExecutor定时任务的)和ThreadPoolExecutor普通的),本文主要介绍ThreadPoolExecutor。

《阿里巴巴java编程手册》并发处理部分,讲了两个原则:

 1. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

  使用线程池的好处是减少在创建和销毁线程上所消耗的时间及系统资源,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

  我们知道,有继承Thread,implements Runnable, implements Callable<T>这些种方法创建线程,但推荐使用线程池来创建线程。

 2. 线程池不允许使用Executors创建,而是通过ThreadPoolExecutor的方式创建,这样的处理方式能让编写代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端如下:

  1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM(无参的LinkedBlockingQueue的长度默认是Integer.MAX_VALUE)

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}

  2)CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}

ThreadPoolExecutor类介绍

ThreadPoolExecutor.class构造方法

//这个是ThreadPoolExecutor完整的构造器,其他的构造器其实也是在内部调用这个.
ThreadPoolExecutor( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)

下我们以ThreadPoolExecutor为例展示下线程池的工作流程图

例子 使用默认的拒绝策略AbortPolicy

public class Demo1 {
public static void main(String[] args) {
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(10);
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, queue, handler);
for (int i = 0; i < 20; i ++){
final int temp = i;
pool.execute(() -> {
System.out.println("客户" + temp + "来了.......");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
pool.shutdown();
}
}
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.kco.test17.demo1.Demo1$$Lambda$1/15497079@ca494b rejected from java.util.concurrent.ThreadPoolExecutor@1a4f24f[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
at com.kco.test17.demo1.Demo1.main(Demo1.java:16)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)
pool-1-thread-1客户0来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-4客户13来了.......
pool-1-thread-2客户3来了.......
pool-1-thread-1客户4来了.......
pool-1-thread-5客户5来了.......
pool-1-thread-3客户6来了.......
pool-1-thread-4客户7来了.......
pool-1-thread-2客户9来了.......
pool-1-thread-1客户8来了.......
pool-1-thread-3客户10来了.......
pool-1-thread-5客户11来了.......
pool-1-thread-4客户12来了.......

从结果看出来,可以看出线程是重复被使用的,而且当执行的任务超过工作队列的容量时,线程确实抛出了异常.

例子2 使用忽略策略 DiscardPolicy

将   RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();改为

  RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();

运行结果如下:

pool-1-thread-1客户0来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-4客户13来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-3客户3来了.......
pool-1-thread-4客户4来了.......
pool-1-thread-1客户5来了.......
pool-1-thread-5客户6来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-3客户7来了.......
pool-1-thread-4客户8来了.......
pool-1-thread-5客户9来了.......
pool-1-thread-1客户10来了.......
pool-1-thread-2客户11来了.......
pool-1-thread-4客户12来了.......

  现在线程池正确退出了,而且也不抛出异常了,但是超过工作队列容量的任务全部被忽略了.

例子3 使用忽略最早任务策略 DiscardOldestPolicy

 RejectedExecutionHandler 改为 RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardOldestPolicy();

pool-1-thread-1客户0来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-4客户13来了.......
pool-1-thread-4客户8来了.......
pool-1-thread-1客户11来了.......
pool-1-thread-5客户10来了.......
pool-1-thread-3客户9来了.......
pool-1-thread-2客户12来了.......
pool-1-thread-1客户15来了.......
pool-1-thread-4客户16来了.......
pool-1-thread-5客户17来了.......
pool-1-thread-2客户19来了.......
pool-1-thread-3客户18来了.......

  从以上结果,我们可以看出除了客户0客户2刚好是3个核心线程被执行后,客户3客户7直接被忽略掉了.

例子4 使用来着不拒策略 CallerRunsPolicy

  同样讲拒绝策略改为RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
运行程序,结果如下:

pool-1-thread-1客户0来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-4客户13来了.......
main客户15来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-2客户3来了.......
pool-1-thread-1客户4来了.......
main客户18来了.......
pool-1-thread-3客户5来了.......
pool-1-thread-4客户7来了.......
pool-1-thread-5客户6来了.......
pool-1-thread-5客户8来了.......
pool-1-thread-1客户9来了.......
pool-1-thread-4客户10来了.......
pool-1-thread-3客户12来了.......
pool-1-thread-2客户11来了.......
pool-1-thread-1客户16来了.......
pool-1-thread-5客户19来了.......
pool-1-thread-3客户17来了.......

  结果,我们可以发现所有的任务都被执行,而且竟然还有两个是在主线程执行的.现在明白我之前说的则直接运行任务的run方法的意思了吧,没错是直接调用run方法,而不是开启线程去执行任务.

例子5 使用自定义的拒绝策略

 现在我们自己写一个拒绝策略,要求所有的任务都必须被线程池执行,而且都要在线程池中执行.

public class Demo5 {
public static void main(String[] args) {
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(10);
RejectedExecutionHandler handler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
if (!executor.isShutdown()){
try {
executor.getQueue().put(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 0, TimeUnit.SECONDS, queue, handler);
for (int i = 0; i < 20; i ++){
final int temp = i;
pool.execute(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + "客户" + temp + "来了.......");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
pool.shutdown();
}
}

 运行结果: 

pool-1-thread-1客户0来了.......
pool-1-thread-3客户2来了.......
pool-1-thread-5客户14来了.......
pool-1-thread-4客户13来了.......
pool-1-thread-2客户1来了.......
pool-1-thread-1客户3来了.......
pool-1-thread-3客户4来了.......
pool-1-thread-5客户5来了.......
pool-1-thread-2客户6来了.......
pool-1-thread-4客户7来了.......
pool-1-thread-1客户8来了.......
pool-1-thread-3客户9来了.......
pool-1-thread-5客户10来了.......
pool-1-thread-4客户11来了.......
pool-1-thread-2客户12来了.......
pool-1-thread-1客户15来了.......
pool-1-thread-3客户16来了.......
pool-1-thread-5客户17来了.......
pool-1-thread-4客户19来了.......
pool-1-thread-2客户18来了.......

  ok.所有任务都被线程池执行了.而且我们自定义的拒绝策略也很简单,就是让工作队列调用put让其一直等待,直到有可用的容量存放任务.

动手自己写一个线程池:

import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.locks.ReentrantLock; public class MyThreadPool {
/**存放线程的集合*/
private ArrayList<MyThread> threads;
/**任务队列*/
private ArrayBlockingQueue<Runnable> taskQueue;
/**线程池初始限定大小*/
private int threadNum;
/**已经工作的线程数目*/
private int workThreadNum; private final ReentrantLock mianLock = new ReentrantLock(); public MyThreadPool(int initPoolNum) {
this.threadNum = initPoolNum;
this.threads = new ArrayList<>(initPoolNum);
//任务队列初始化为线程池线程数的四倍
this.taskQueue = new ArrayBlockingQueue<>(initPoolNum*4);
this.workThreadNum = 0;
} public void execute(Runnable runnable) {
try {
mianLock.lock();
//线程池未满,每加入一个任务则开启一个线程
if (this.workThreadNum < this.threadNum) {
MyThread myThread = new MyThread(runnable);
myThread.start();
threads.add(myThread);
this.workThreadNum++;
} else { //线程池已满,放入任务队列,等待有空闲线程时执行
//队列已满,无法添加时,拒绝任务
if (!taskQueue.offer(runnable)) {
rejectTask();
}
}
} finally {
mianLock.unlock();
}
} private void rejectTask() {
System.out.println("任务队列已满,无法继续添加,请扩大您的初始化线程池!");
} public static void main(String[] args) {
MyThreadPool myThreadPool = new MyThreadPool(5);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行中");
}
};
for (int i = 0; i < 20; i++) {
myThreadPool.execute(task);
}
} class MyThread extends Thread {
private Runnable task; public MyThread(Runnable runnable) {
this.task = runnable;
} @Override
public void run() {
//该线程一直启动着,不断从任务队列取出任务执行
while(true) {
//如果初始化任务不为空,则执行初始化任务
if(task != null) {
task.run();
task = null;
}else { //否则去任务队列取任务并执行
Runnable queueTask = taskQueue.poll();
if(queueTask != null)
queueTask.run();
}
}
}
}
}

java 线程池 - ThreadPoolExecutor的更多相关文章

  1. Java线程池ThreadPoolExecutor使用和分析(三) - 终止线程池原理

    相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...

  2. Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理

    相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...

  3. Java线程池ThreadPoolExecutor使用和分析(一)

    相关文章目录: Java线程池ThreadPoolExecutor使用和分析(一) Java线程池ThreadPoolExecutor使用和分析(二) - execute()原理 Java线程池Thr ...

  4. Java线程池ThreadPoolExecutor类源码分析

    前面我们在java线程池ThreadPoolExecutor类使用详解中对ThreadPoolExector线程池类的使用进行了详细阐述,这篇文章我们对其具体的源码进行一下分析和总结: 首先我们看下T ...

  5. java线程池ThreadPoolExecutor使用简介

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

  6. Java 线程池(ThreadPoolExecutor)原理分析与使用

    在我们的开发中"池"的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 使用线程池的好处 1.降低资源消耗 可以重复利用 ...

  7. Java 线程池(ThreadPoolExecutor)原理解析

    在我们的开发中“池”的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 有关java线程技术文章还可以推荐阅读:<关于java多线程w ...

  8. Java线程池(ThreadPoolExecutor)原理分析与使用

    在我们的开发中"池"的概念并不罕见,有数据库连接池.线程池.对象池.常量池等等.下面我们主要针对线程池来一步一步揭开线程池的面纱. 使用线程池的好处 1.降低资源消耗 可以重复利用 ...

  9. 转:JAVA线程池ThreadPoolExecutor与阻塞队列BlockingQueue

    从Java5开始,Java提供了自己的线程池.每次只执行指定数量的线程,java.util.concurrent.ThreadPoolExecutor 就是这样的线程池.以下是我的学习过程. 首先是构 ...

  10. java线程池ThreadPoolExecutor的使用

    package s.b.foo.caze.thread; import java.io.Serializable; import java.util.concurrent.ArrayBlockingQ ...

随机推荐

  1. Struts2笔记(学struts2只需要这一篇文章)

    1.如何将struts2框架引入到web项目中      1.把struts2相关jar包引入到项目中 2.把struts2的配置文件直接放到src下面,名字要叫做struts.xml.(运行时配置文 ...

  2. jQuery_完成表单注册检验

    在校验表单的时候会很麻烦,但是jq可以用很简单的方法来检验,即使用validate. 原表单: 代码 <!DOCTYPE html> <html> <head> & ...

  3. spring注解版

    第一.spring框架快速入门 1.1什么是spring 框架 Spring 框架是 Java 应用最广的框架,它的成功来源于理念,而不是技术本身,它的理念包括 IoC (Inversion of C ...

  4. Unity3D_(API)Random随机数

    Unity随机数Random官方文档: 传送门 一.生成一个随机数 二.Random.InitState()生成伪随机数 三.官方文档中常用的方法 创建一个Cube用来挂载Random_Gary.cs ...

  5. 关键字transient是干啥的

    百度百科的解释: Java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持.换句话来说就是,用transient关键字标记的成员变量不参与序列化过程. ...

  6. 分布式-网络通信-IO-基础(1)

    IO整体图架构  一.IO流概述 概述: IO流简单来说就是Input和Output流,IO流主要是用来处理设备之间的数据传输,Java对于数据的操作都是通过流实现,而java用于操作流的对象都在IO ...

  7. 数据框架对比:Hadoop、Storm、Samza、Spark和Flink——flink支持SQL,待看

    简介 大数据是收集.整理.处理大容量数据集,并从中获得见解所需的非传统战略和技术的总称.虽然处理数据所需的计算能力或存储容量早已超过一台计算机的上限,但这种计算类型的普遍性.规模,以及价值在最近几年才 ...

  8. golang channel关闭后,是否可以读取剩余的数据

    golang channel关闭后,其中剩余的数据,是可以继续读取的. 请看下面的测试例子. 创建一个带有缓冲的channel,向channel中发送数据,然后关闭channel,最后,从channe ...

  9. 【DVWA】SQL Injection(SQL 注入)通关教程

    日期:2019-07-28 20:43:48 更新: 作者:Bay0net 介绍: 0x00.基本信息 关于 mysql 相关的注入,传送门. SQL 注入漏洞之 mysql - Bay0net - ...

  10. Python文件重命名代码

    import os def re_name(path): for file in os.listdir(path): file_path = os.path.join(path, file) # 判断 ...