一、前言
定义
线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。
使用线程池的优势
- 1、线程和任务分离,提升线程重用性;
- 2、控制线程并发数量,降低服务器压力,统一管理所有线程;
- 3、提升系统响应速度,假如创建线程用的时间为T1,执行任务用的时间为T2,销毁线程用的时间为T3,那么使用线程池就免去了T1和T3的时间;
为什么要是用线程池
可以根据系统的需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行运行压力
二、Executor框架
(一)什么是Executor框架?
我们知道线程池就是线程的集合,线程池集中管理线程,以实现线程的重用,降低资源消耗,提高响应速度等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。
Executor框架的成员
三、Executor框架结构
1、Executor框架包括3大部分:
(1)任务。也就是工作单元,包括被执行任务需要实现的接口:Runnable接口或者Callable接口;
(2)任务的执行。也就是把任务分派给多个线程的执行机制,包括Executor接口及继承自Executor接口的ExecutorService接口。
(3)异步计算的结果。包括Future接口及实现了Future接口的FutureTask类。
2、Executor框架的使用示意图
Runnable对象、Callable对象:
使用步骤:https://blog.csdn.net/fendouderen/article/details/126562398
四、创建线程池的方式
(一)通过 ThreadPoolExecutor 构造函数实现
定义MyRunnable类实现Runnable接口,并实现run方法
public class MyRunnable implements Runnable{
String name;
public MyRunnable(String name){
this.name = name;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + name + "上山");
}
}
使用ThreadPoolExecutor创建线程池
public class RealPool {
//核心线程数
static int corePoolSize = 3;
//最大线程数
static int maximumPoolSize = 6;
//超过 corePoolSize 线程数量的线程最大空闲时间
static long keepAliveTime = 2;
//以秒为时间单位
static TimeUnit unit = TimeUnit.SECONDS;
//对应创建工作队列,用于存放提交的等待执行任务,此处填写队列大小
static int queueSize = 20000;
public static void main(String[] args) {
Long start = System.currentTimeMillis();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
new ArrayBlockingQueue<>(queueSize),
new ThreadPoolExecutor.AbortPolicy()
);
MyRunnable r1 = new MyRunnable("realPool");
MyRunnable r2 = new MyRunnable("狗狗");
MyRunnable r3 = new MyRunnable("猫猫");
executor.execute(r1);
executor.execute(r2);
executor.execute(r3);
executor.shutdown();
Long end = System.currentTimeMillis();
System.out.println("花费时间为:"+ (end-start) + "ms");
}
}
execute与submit
提交任务的类型:
- execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务
- submit既能提交Runnable类型任务也能提交Callable类型任务。
异常: - execute会直接抛出任务执行时的异常,可以用try、catch来捕获,和普通线程的处理方式完全一致
- submit会吃掉异常,可通过Future的get方法将任务执行时的异常重新抛出。
返回值: - execute()没有返回值
- submit有返回值,所以需要返回值的时候必须使用submit
public void execute(Runnable command) {
// 如果实现类是null,则报NullPointerException错误
if (command == null)
throw new NullPointerException();
// 主池控制状态ctl是一个原子整数,包装了两个概念字段workerCount,表示有效线程数runState,表示是否正在运行、正在关闭等为了将它们打包成一个int
// 获取线程的状态
int c = ctl.get();
// 如果当前工作线程少于核心线程,则加一个线程,并把实现类放进去
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 如果当前执行的任务数量大于等于 corePoolSize 的时候就会走到这里
// 如果线程池正执行,且队列能放新元素,则执行内部
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
再次获取线程池状态,如果线程池状态不是 RUNNING 状态就需要从任务队列中移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
if (! isRunning(recheck) && remove(command))
reject(command);
// 如果当前线程池为空就新创建一个线程并执行。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 创建线程后执行失败,则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
(二)通过工厂类 Executors 来实现
不建议使用
在《阿里巴巴java开发手册》中指出了线程资源必须通过线程池提供,不允许在应用中自行显示的创建线程,这样一方面是线程的创建更加规范,可以合理控制开辟线程的数量;另一方面线程的细节管理交给线程池处理,优化了资源的开销。
而线程池不允许使用Executors去创建,而要通过ThreadPoolExecutor方式,这一方面是由于jdk中Executor框架虽然提供了如newFixedThreadPool()、newSingleThreadExecutor()、newCachedThreadPool()等创建线程池的方法,但都有其局限性,不够灵活;另外由于前面几种方法内部也是通过ThreadPoolExecutor方式实现,使用ThreadPoolExecutor有助于大家明确线程池的运行规则,创建符合自己的业务场景需要的线程池,避免资源耗尽的风险
具体实现
https://blog.csdn.net/fendouderen/article/details/126562398