参考文献:
- Android 多线程: 完全解析线程池ThreadPool原理&使用
- Android开发——Android中常见的4种线程池(保证你能看懂并理解)
- Java并发编程:线程池的使用
1 简介
除了①线程池,使用线程还有三种方式,分别是 ②继承Thread类 ③实现Runnable接口④实现Callable接口,这三种方式最后都需要新建和销毁线程。在实际的高并发场景下,往往线程数多,每个线程执行任务耗时短。上面三种新建线程的方式,新建和销毁线程的时间消耗经常会比实际执行任务的耗时还要长,导致提交的任务不能立即响应执行,并且新建和销毁线程往往有一定的资源消耗;其次创建新线程的方式线程缺乏统一管理,容易出现线程阻塞的情况。所以这里我们引入了一种复用线程执行任务的方式-线程池,减少新建线程的次数。
首先说说“任务”的概念,实现Runnable或Callable接口创建的对象只能当做一个可以在线程中运行的任务,不是真正意义上的线程,所以最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
2 原理
2.1 内部逻辑结构
2.2 核心参数
ThreadPoolExecutor
类是线程池中最重要的类,可以通过在该类实例化时配置不同的参数传入构造器,来实现自定义线程池;
// 创建线程池对象,通过 构造方法 配置核心参数
Executor executor = new ThreadPoolExecutor( CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue,sThreadFactory );
// 构造函数
public ThreadPoolExecutor (int corePoolSize,int maximumPoolSize,long keepAliveTime,
TimeUnit unit,BlockingQueue,
ThreadFactory threadFactory )
2.3 任务执行逻辑
补充说明:
- 处理任务优先级:核心线程 > 任务队列 > 非核心线程 > 拒绝策略handler;
- 非核心线程空闲时间超过存活期keepAliveTime,就会被回收;一般情况,核心线程即使闲置也不会被回收;(除非当allowCoreThreadTimeOut(true)时,keepAliveTime也适用于核心线程);
- 一般情况,线程池刚创建时,其中是没有线程的,只有新任务到来,才会创建线程执行该任务;
对于线程池、核心线程、非核心线程自己的通俗理解:
线程池看成一个公司,核心线程可以看成是看成是公司的正式员工,非核心线程可以看成是实习生;无论是正式员工还是实习生在同一时刻只能执行一个任务。当有新任务时,老板首先找正式员工干活;如果正式员工都在干活时继续来新任务,那么多余任务就放到一个任务队列里,等有正式员工闲下来后接着干新任务;如果任务较多,超出正式员工能处理的数量,这时老板就考虑招几个实习生来干活,实习生处理任务队列满了之后新来的任务。如果任务太多了,那么老板就不再接单了,拒绝处理新来的任务。
一般情况,正式员工有任务到来就干活,没任务就空闲,不会被裁;实习生有任务到来就干活,没任务就空闲,空闲的时间太长了,老板不养闲人,就把这个实习生裁了。
3 基本使用
// 1.创建线程池,通过配置核心参数,从而实现自定义线程池
Executor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,
TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);
// 注:在Java中,已内置4种常用线程池,下面会详细说明
// 2.向线程池提交任务:execute(),传入Runnable对象
threadPool.execute(new Runnable() {
@Override
public void run() {
... // 线程执行任务
}
});
// 3. 关闭线程池shutdown()
threadPool.shutdown();
shutdown()
和shutdownNow()
区别:
-
shutdown()
:等待正在执行任务的线程执行完再关闭线程池; -
shutdownNow()
:不等待立即关闭;
4 四类常用线程池
根据参数的不同配置,Java内置了4种常用线程池,他们的参数已经配置好了:
- 定长线程池(FixedThreadPool)
- 定时线程池(ScheduledThreadPool )
- 缓存线程池(CachedThreadPool)
- 单例线程池(SingleThreadExecutor)
4.1 定长线程池FixedThreadPool
特点:只有核心线程 & 不会被回收、线程数固定、任务队列无大小限制;
应用场景:控制线程池最大并发数;
//创建 定长线程池 对象 & 设置线程池线程数固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//向线程池提交任务:execute()
fixedThreadPool.execute(() -> System.out.println("定长线程池->执行任务啦"));
//关闭线程池
fixedThreadPool.shutdown();
4.2 定时线程池ScheduledThreadPool
特点:核心线程数固定、非核心线程数无限制(闲时立刻回收);
应用场景:执行定时 / 周期性 任务
//创建 定时线程池 对象 & 设置线程池核心线程数固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//创建好Runnable类线程对象 & 需执行的任务
Runnable task1 = () -> System.out.println("定时线程池->执行任务啦");
// 向线程池提交任务:schedule()
scheduledThreadPool.schedule(task1, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task1,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务
//关闭线程池
scheduledThreadPool.shutdown();
4.3 缓存线程池CachedThreadPool
- 特点:只有非核心线程、线程数无限制;灵活回收空闲线程(具备超时机制,全部回收时几乎不占系统资源);无线程可用时新建线程;
- 应用场景:执行数量多、耗时少任务
// 创建 可缓存线程池 对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//向线程池提交任务1:execute()
cachedThreadPool.execute(() -> System.out.println("可缓存线程池-执行任务1"));
//向线程池提交任务2:execute();复用线程
cachedThreadPool.execute(() -> System.out.println("可缓存线程池-执行任务2"));
//关闭线程池
cachedThreadPool.shutdown();
4.4 单例线程池SingleThreadExecutor
特点:只有一个核心线程,保证所有任务按顺序在一个线程中执行,不存在线程安全问题;
应用场景:适合单线程任务;不适合必须使用多线程的耗时任务,如数据库操作、文件操作等;
//创建 单例线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
//向线程池提交任务:execute()
singleThreadExecutor.execute(() -> System.out.println("单例线程池-执行任务啦"));
//关闭线程池
singleThreadExecutor.shutdown();