java线程池ThreadPoolExecutor

本文详细介绍了Java线程池ThreadPoolExecutor的使用,包括核心参数(如核心线程数、最大线程数、存活时间等)、执行流程、线程池状态及核心方法。线程池通过减少线程创建销毁的开销,提高系统资源利用率。文章还讨论了四种预定义的线程池类型,并解析了线程池的任务处理流程和线程池状态变化。
摘要由CSDN通过智能技术生成

一、ThreadPoolExecutor应用

为什么使用线程池?答:降低线程创建和关闭的消耗。当我们想同时开启200个线程,自己手动创建线程,使用后再销毁,会导致资源占用过多,所以应考虑使用线程池。线程池里有很多提前备好的可用线程资源,如果需要就直接从线程池里拿;不用的时候,线程池会自动帮我们管理。

使用线程池主要有以下两个好处:

  • 减少在创建和销毁线程上所花的时间以及系统资源的开销
  • 不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存

JDK提供了一个类Executors,还有好多包装好的线程池,但是参数是固定的,所以常常要自己通过类ThreadPoolExecutor自定义线程池的属性。java提供的定义好的线程池:

  1. FixedThreadPool:创建固定大小的线程池,数量通过传入的参数决定。
  2. SingleThreadExecutor:创建一个线程容量的线程池,所有的线程依次执行,相当于创建固定数量为 1 的线程池。
  3. CachedThreadPool:创建可缓存的线程池,没有最大线程限制(实际上是 Integer.MAX_VALUE)。如果有空闲线程等待时间超过一分钟,就关闭该线程。
  4. ScheduledThreadPool:创建计划 (延迟) 任务线程池, 线程池中的线程可以被设置为在特定的延迟时间之后开始执行,也可以以固定的时间重复执行(周期性执行)。
  5. SingleThreadScheduledExecutor:创建线程池延迟任务,创建一个线程容量的计划任务。

简单理解一下线程池的工作流:池内包括核心线程、非核心线程、阻塞队列、线程工厂。

  1. 当一个任务被分配给线程池,池会优先找到一个空闲的核心线程来完成这个任务。
  2. 如果没有空闲的核心线程,就创建或找到一个非核心线程来进行这个任务。一般我们会设置核心线程一直在池内存在,但非核心线程不会,当一个核心线程的空闲时间达到了我们设置的一个时间长度,该非核心线程就会被回收。
  3. 如果所有线程都在忙,并且已经达到了池内最大线程数的限制,那么就将此任务放入等待队列,等待线程空闲下来后,再来处理这些等待中的任务。
  4. 如果这三个地方都放满了,就要使用拒绝策略了,也就是以一定的策略来处理这个无处安放的任务。拒绝策略一般包括直接抛弃这个任务,或者将队头等待时间过长的一个任务删除,把当前新任务加入队列,后面会有详细介绍。
    在这里插入图片描述

一个简单使用的例子:

	ThreadPoolExecutor executor = new ThreadPoolExecutor( // 1.创建线程池对象
                1, // 核心线程数
                2, // 最大线程数量,即核心和非核心线程数量之和
                500, // 非核心线程的最大空闲时间,查过这个时间,相应非核心线程就会被回收
                TimeUnit.SECONDS, // 上一个参数的时间单位
                new LinkedBlockingDeque<>(), // 阻塞队列设置
                new ThreadFactory() {
    // 生产线程的工厂,实现接口ThreadFactory
                    @Override // 重写创建新线程的方法
                    public Thread newThread(Runnable r) {
   
                        Thread t = new Thread(r);
                        t.setName("测试-"); // 可以自定义设置新线程的名字
                        return t;
                    }
                },
                new ThreadPoolExecutor.AbortPolicy() // 设置拒绝策略
        );

        executor.execute(new Runnable() {
    // 2.调用execute或submit方法执行Runnable任务或Callable任务
            @Override
            public void run() {
   
                System.out.println("love hy");
            }
        });

        Future<Object> future = executor.submit(new Callable<Object>() {
   
            @Override
            public Object call() throws Exception {
   
                return null;
            }
        });
        future.get();

二、ThreadPoolExecutor核心参数

    public ThreadPoolExecutor(int corePoolSize, // 核心线程数
                              int maximumPoolSize, // 最大线程数(非核心线程数)
                              long keepAliveTime, // 非核心线程的最大空闲时间
                              TimeUnit unit, // keepAliveTime的单位
                              BlockingQueue<Runnable> workQueue, // 等待队列,可以使用阻塞队列或工作队列
                              ThreadFactory threadFactory, // 线程工厂
                              RejectedExecutionHandler handler) // 拒绝策略

1、核心线程数

核心线程在池中的数量,核心线程会一直在池中等待任务到来,即使空闲也会一直存在,除非设置了allowCoreThreadTimeOut,核心线程才会在空闲一段时间后被回收。

到来的任务会优先分配给核心线程执行。

2、最大线程数

例如此参数设置为3,核心线程数为2,等待队列最大容量为2,此时来了5个任务,2个在被核心线程执行,2个进入等待队列,还有一个会被非核心线程执行,最大线程数就规定了最多有几个核心和非核心线程

3、keepAliveTime

非核心线程不会一直存在,此参数就是非核心线程的最大空闲时间,超过这个时间,非核心线程就会消失

4、阻塞队列

最常用的是ArrayBlockingQueue、LinkedBlockingQueue

5、线程工厂

线程池创建后,默认是空的,任务进入线程池后,线程才被创建。线程工厂使得我们程序员可以控制对于线程的创建,从而方便后续对这些线程的操作(如通过线程名字找到线程对象)

6、拒绝策略

当核心线程、非核心线程都在运行,且等待队列已经满了的时候,又来了新任务,如何拒绝这个任务。RejectedExecutionHandler是一个接口,只有一个抽象方法rejectedExecution(Runnable r, ThreadPoolExecutor executor),实现类有四个。实现类AbortPolicy对rejectedExecution的实现直接抛出了一个异常,意思是线程池无法处理多出的这个任务。实现类CallerRunsPolicy对rejectedExecution的实现是让main线程执行这个新线程:

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
   
            if (!e.isShutdown()) {
   
                r.run();
            }
        }

实现类DiscardOldestPolicy重写的rejectedExecution方法将等待队列的队头弹出,将新线程推入等待队列:

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
   
            if (!e.isShutdown()) {
   
                e.getQueue().poll(); // 把等待队列对头弹出
                e.execute(r); // 新线程加入线程池
            }
        }

实现类DiscardPolicy更绝,重写方法啥也不做:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
   
}

可以看出,拒绝策略比较好实现,业务中常常由程序员自己实现。

三、ThreadPoolExecutor执行流程

即业务线程提交任务到线程池后,任务的处理流程。

源码:

public void execute(Runnable command) {
   
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
    // 判断当前池内的工作线程数是否小于核心线程数
        // 如果小于,就创建核心线程并执行任务
            if (addWorker(command, true))
                
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadPoolExecutorJava 中的一个线程池实现类,可以用来管理和执行线程。它提供了一些方法来创建、配置和管理线程池,可以控制线程池的大小、任务队列、拒绝策略等。 以下是一个使用 ThreadPoolExecutor 的示例: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; public class ThreadPoolExample { public static void main(String[] args) { // 创建一个线程池,最多有5个线程,任务队列最多有10个任务 ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(5); // 提交10个任务到线程池 for (int i = 1; i <= 10; i++) { Task task = new Task("Task " + i); executor.execute(task); } // 关闭线程池 executor.shutdown(); } static class Task implements Runnable { private String name; public Task(String name) { this.name = name; } @Override public void run() { System.out.println("Executing " + name + " on " + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Finished " + name + " on " + Thread.currentThread().getName()); } } } ``` 上面的代码创建了一个最多有5个线程的线程池,然后提交了10个任务到线程池。每个任务都是一个简单的 Runnable 对象,执行完任务后会输出一些信息。 注意,线程池要调用 shutdown() 方法来关闭,否则程序不会退出。在关闭之前,可以通过调用 getActiveCount() 方法来获取当前线程池中正在执行的任务数。当任务数为0时,线程池才会真正关闭。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值