Day_27 【Java基础】线程池ThreadPoolExecutor、ExecutorService、Executors工具类详解【附源码】

一、线程池

1. 什么是线程池?
从JDK 5开始出现,线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。(这里的任务就是实现了Runnable或Callable接口的实例对象)
2.线程池有什么优势?
① 降低资源消耗(重复利用线程池中的线程)
② 提高响应速度(减少了创建新线程的时间)
③ 便于线程管理(线程池统一分配,调度线程)

二、ExecutorService接口

//Executor是线程的顶级接口,提供了包括线程使用,调度等功能。
//Executor接口只定义了一个抽象方法void execute(Runnable command):在将来的某个时间执行给定的命令。  
public interface ExecutorService extends Executor {...}

在这里插入图片描述
1.获取ExecutorService
可以利用Executors工具类中的静态方法获取ExecutorService,常见的如下:
在这里插入图片描述
(1)定长线程池(newFixedThreadPool)
特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
应用场景:控制线程最大并发数。

//newFixedThreadPool举例说明
@Test
public void test01(){
    //1. 创建定长线程池对象 & 设置线程池线程数量固定为3
    ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
    //2. 创建好Runnable类线程对象 & 需执行的任务
    Runnable task = new Runnable() {//此时线程池中只有一个线程
        public void run() {
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"正在执行任务...");
            }
        }
    };
    //3. 向线程池提交任务
    fixedThreadPool.execute(task);
    //4.关闭线程池
    fixedThreadPool.shutdown();
}

(2)定时线程池(ScheduledThreadPool )
特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
应用场景:执行定时或周期性的任务。

//1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
//2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task = new Runnable() {
    public void run() {
        System.out.println("正在执行任务...");
    }
};
//3. 向线程池提交任务
scheduledThreadPool.schedule(task, 5, TimeUnit.SECONDS); // 延迟1s后执行任务
        scheduledThreadPool.scheduleAtFixedRate(task, 10, 1000, TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务

(3)可缓存线程池(CachedThreadPool)
特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
应用场景:执行大量、耗时少的任务。

// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("正在执行任务...");
  }
};
// 3. 向线程池提交任务
cachedThreadPool.execute(task);

(4)单线程化线程池(SingleThreadExecutor)
特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
singleThreadExecutor.execute(task);

三、ThreadPoolExecutor

ExecutorService 是接口,无法接创建对象,因此线程池的真正实现类是 ThreadPoolExecutor,其构造方法有如下4种:

//1.创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂和拒绝执行处理程序。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
//2.创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
//3.创建一个新的 ThreadPoolExecutor与给定的初始参数和默认拒绝执行处理程序。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
//4.创建一个新 ThreadPoolExecutor给定的初始参数。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)

当构造函数出现以下条件,会抛出如下异常:
在这里插入图片描述
从以上可以看出,ThreadPoolExecutor类构造方法都具有如下的参数:
在这里插入图片描述
2.线程池工作流程
在这里插入图片描述
2.1 处理任务的流程和优先级

核心线程(corePoolSize)—>任务队列(workQueue)—>最大线程(maximumPoolSize)
当线程池中的线程数量 > 核心线程(corePoolSize)时,若某线程(非核心线程)的空闲时间 > 闲置超时时长(keepAliveTime) ,线程将被终止;(通过这样的策略,线程池可动态调整池中的线程数)

① 是否达到核心线程数量?没达到,创建一个工作线程来执行任务。
② 工作队列是否已满?没满,则将新提交的任务存储在工作队列里。
③ 是否达到线程池最大数量?没达到,则创建一个新的工作线程来执行任务。最后,执行拒绝策略来处理这个任务。
在这里插入图片描述

// 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(); 
  // 关闭线程的原理
  // a. 遍历线程池中的所有工作线程
  // b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)
  // 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
  // 二者区别:
  // shutdown:设置线程池的状态为 SHUTDOWN,然后中断所有没有正在执行任务的线程
  // shutdownNow:设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
  // 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()

线程池的状态:Running、ShutDown、Stop、Tidying、Terminated。
在这里插入图片描述

四、线程池使用案例

假如某网上商城推出活动,新上架5部新手机免费送客户体验,要求所有参与活动的人员在规定的时间同时参与秒杀挣抢,假设有10人同时参与了该活动,请使用线程池模拟这个场景,保证前5人秒杀成功,后5人秒杀失败;
要求:
① 使用线程池创建线程
② 解决线程安全问题
思路提示:
① 既然商品总数量是5个,那么我们可以在创建线程池的时候初始化线程数是5个及以下,设计线程池最大数量为5个;
② 当某个线程执行完任务之后,可以让其他参与秒杀的人继续使用该线程参与秒杀;
③ 使用synchronized控制线程安全
代码步骤:
① 编写任务类,主要是送出手机给秒杀成功的客户;
② 编写主程序类,创建10个任务(模拟10个客户);
③ 创建线程池对象并接收10个任务,开始执行任务。

/**
 * ThreadPoolExecutor线程池模拟秒杀系统,10个客户对5个商品进行秒杀
 * @author wds
 * @date 2021-12-02-18:40
 */
public class ThreadPoolTest02 {
    public static void main(String[] args) {
        //1.创建线程池(corePoolSize:3,maximumPoolSize:5,keepAliveTime:1,TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(15))
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 5, 1,
                TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(15));
        //2.创建任务对象(实现类对象)
        for (int i=1;i<=10;i++){
            MyTask myTask = new MyTask("客户" + i);
//            Future<?> submit = threadPoolExecutor.submit(myTask);
//            System.out.println(submit);
            //问题一:execute(Runnable command)和submit(Runnable task)有什么区别?
            threadPoolExecutor.execute(myTask);
        }
        //3.关闭线程池
//        threadPoolExecutor.shutdownNow();
        //问题二:shutdown()和shutdownNow()有什么区别?
        threadPoolExecutor.shutdown();
    }
}
//任务对象(实现Runnable接口,重写run()方法)
class MyTask implements Runnable{
    private static int num = 5;//商品数量
    private String username;//客户姓名
    public MyTask(String username) {
        this.username = username;
    }
    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println(username+"正在通过"+name+"参与秒杀");
        try {
            Thread.sleep(100);//模拟秒杀需要的时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (MyTask.class){
            if(num>0){
                System.out.println(username+"使用"+name+"秒杀:"+num--+"号商品成功了!");//秒杀成功,商品数减1
            }else{
                System.out.println(username+"使用"+name+"秒杀商品失败了...");
            }
        }
    }
}

在这里插入图片描述
参考技术博客
Java 多线程:彻底搞懂线程池
Android多线程:线程池ThreadPool全面解析

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

智商三岁半i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值