Java多线程系列详解_05_使用ThreadPoolExecutor自定义创建线程池

本文详细解析了ThreadPoolExecutor的构造参数,包括核心池大小corePoolSize、最大线程数maximumPoolSize、存活时间keepAliveTime、工作队列workQueue、线程工厂threadFactory和拒绝策略handler。重点介绍了常用工作队列ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue的特性和使用场景。此外,还讨论了RejectedExecutionHandler的四种常见策略。最后通过实例展示了自定义线程池的测试结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.构造详解

ThreadPoolExecutor有四个构造
在这里插入图片描述
讲解最详细的如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
序号名称类型含义
1corePoolSizeint核心线程池大小
2maximumPoolSizeint最大线程池大小
3keepAliveTimelong线程最大空闲时间
4unitTimeUnit时间单位
5workQueueBlockingQueue线程等待队列
6threadFactoryThreadFactory线程创建工厂
7handlerRejectedExecutionHandler拒绝策略

1. int corePoolSize :

  • 核心池的大小,创建了线程池后不会创建线程,直到有任务来才会执行创建线程;
  • prestartAllCoreThreads()或者prestartCoreThread()会初始化线程数;
  • 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

2. int maximumPoolSize :

程池最大线程数, 它表示在线程池中最多能创建多少个线程

3. long keepAliveTime :

  • 空闲线程超时时长, 只有当线程数超过corePoolSize后的线程才会有效;
  • allowCoreThreadTimeOut(boolean)会导致线程数不超过corePoolSize也会生效

4. TimeUnit unit :

  • 参数keepAliveTime的时间单位
        //天
        TimeUnit days = TimeUnit.DAYS;
        //小时
        TimeUnit hours = TimeUnit.HOURS;
        //分钟
        TimeUnit minutes = TimeUnit.MINUTES;
        //秒
        TimeUnit seconds = TimeUnit.SECONDS;
        //毫秒
        TimeUnit milliseconds = TimeUnit.MILLISECONDS;
        //微妙
        TimeUnit microseconds = TimeUnit.MICROSECONDS;
        //纳秒
        TimeUnit nanoseconds = TimeUnit.NANOSECONDS;

5. BlockingQueue workQueue :

  • 一个阻塞队列, 用来存储等待执行的任务

6. ThreadFactory threadFactory :

  • 创建线程的工厂

7. RejectedExecutionHandler handler :

  • 拒绝处理任务时的策略

2.参数详解

1. BlockingQueue workQueue接口常用实现类

BlockingQueue 接口的常用方法:

入队:
offer(E e):如果队列没满,立即返回true; 如果队列满了,立即返回false–>不阻塞
put(E e):如果队列满了,一直阻塞,直到队列不满了或者线程被中断–>阻塞
offer(E e, long timeout, TimeUnit unit):在队尾插入一个元素,,如果队列已满,则进入等待,直到出现以下三种情况:–>阻塞
被唤醒 / 等待时间超时 / 当前线程被中断

出队
poll():如果没有元素,直接返回null;如果有元素,出队
take():如果队列空了,一直阻塞,直到队列不为空或者线程被中断–>阻塞
poll(long timeout, TimeUnit unit):如果队列不空,出队;如果队列已空且已经超时,返回null;如果队列已空且时间未超时,则进入等待,直到出现以下三种情况:
被唤醒 / 等待时间超时 / 当前线程被中断

BlockingQueue 常见实现类 :

  • ArrayBlockingQueue(不常用)
  • LinkedBlockingQueue(常用)
  • SynchronousQueue(常用)
  • PriorityBlockingQueue(不常用)

1. ArrayBlockingQueue

  • 底层基于数组实现

  • 初始化需要指定容量 , 不可扩容 , 属于有界队列

  • 在队列全满时执行入队将会阻塞,在队列为空时出队同样将会阻塞。

  • 并发阻塞是通过ReentrantLock和Condition来实现的

  • 内部只有一把锁,意味着同一时刻只有一个线程能进行入队或者出队的操作。

  • 方法详解:

    • final Object[] items : 核心数组 , 存储元素
    • final ReentrantLock lock : 可重入锁 , 入队和出队使用一个锁
    • private final Condition notEmpty : 不为空条件 , 出队使用
    • private final Condition notFull : 不为满条件 , 入队使用
    • void put(E e) : 入队方法, 将元素添加到尾部,如果队列满了,则一直阻塞等待有空位再入队
    • boolean add(E e) : 入队方法, 将元素添加到尾部,如果队列满了,则直接抛出异常IllegalStateException
    • boolean offer(E e) : 入队方法,将元素添加到尾部,如果队列满了,则直接返回false,而add()则会直接抛出异常(此方法优先于add())
    • E poll() : 出队方法,移除并将顶部元素返回,如果队列为空,则返回null
    • E take() : 出队方法,移除并将顶部元素返回,如果队列空了,一直阻塞,直到队列不为空或者线程被中断
    • E peek() : 出队方法,将顶部元素返回, 如果队列为空,则返回null
    • E remove() : 出队方法,删除并当前对象,并返回队列头部的元素,如果队列为空,则抛出一个NoSuchElementException异常

2. LinkedBlockingQueue

LinkedBlockingQueue中维持两把锁,一把锁用于入队,一把锁用于出队,这也就意味着,同一时刻,只能有一个线程执行入队,其余执行入队的线程将会被阻塞;同时,可以有另一个线程执行出队,其余执行出队的线程将会被阻塞。换句话说,虽然入队和出队两个操作同时均只能有一个线程操作,但是可以一个入队线程和一个出队线程共同执行,也就意味着可能同时有两个线程在操作队列,那么为了维持线程安全,LinkedBlockingQueue使用一个AtomicInterger类型的变量表示当前队列中含有的元素个数,所以可以确保两个线程之间操作底层队列是线程安全的。

  • LinkedBlockingQueue不允许元素为null。

  • 同一时刻,只能有一个线程执行入队操作,因为putLock在将元素插入到队列尾部时加锁了

  • 如果队列满了,那么将会调用notFull的await()方法将该线程加入到Condition等待队列中。await()方法就会释放线程占有的锁,这将导致之前由于被锁阻塞的入队线程将会获取到锁,执行到while循环处,不过可能因为由于队列仍旧是满的,也被加入到条件队列中。

  • 一旦一个出队线程取走了一个元素,并通知了入队等待队列中可以释放线程了,那么第一个加入到Condition队列中的将会被释放,那么该线程将会重新获得put锁,继而执行enqueue()方法,将节点插入到队列的尾部

  • 然后得到插入一个节点之前的元素个数,如果队列中还有空间可以插入,那么就通知notFull条件的等待队列中的线程。

  • 通知出队线程队列为空了,因为插入一个元素之前的个数为0,而插入一个之后队列中的元素就从无变成了有,就可以通知因队列为空而阻塞的出队线程了。

  • 当队列为空时,就加入到notEmpty(的条件等待队列中,当队列不为空时就取走一个元素,当取完发现还有元素可取时,再通知一下自己的伙伴(等待在条件队列中的线程);最后,如果队列从满到非满,通知一下put线程。

  • LinkedBlockingQueue是允许两个线程同时在两端进行入队或出队的操作的,但一端同时只能有一个线程进行操作,这是通过两把锁来区分的;

  • 为了维持底部数据的统一,引入了AtomicInteger的一个count变量,表示队列中元素的个数。count只能在两个地方变化,一个是入队的方法(可以+1),另一个是出队的方法(可以-1),而AtomicInteger是原子安全的,所以也就确保了底层队列的数据同步。

3. SynchronousQueue

SynchronousQueue–基于CAS

总结:

ArrayBlockingQueue:

  • 一个对象数组+一把锁+两个条件
  • 入队与出队都用同一把锁
  • 在只有入队高并发或出队高并发的情况下,因为操作数组,且不需要扩容,性能很高
  • 采用了数组,必须指定大小,即容量有限

LinkedBlockingQueue:

  • 一个单向链表+两把锁+两个条件
  • 两把锁,一把用于入队,一把用于出队,有效的避免了入队与出队时使用一把锁带来的竞争。
  • 在入队与出队都高并发的情况下,性能比ArrayBlockingQueue高很多
  • 采用了链表,最大容量为整数最大值,可看做容量无限

2.RejectedExecutionHandler handler接口常用实现类

  • AbortPolicy:直接抛出异常。
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
  • 也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。(常用)

3.自定义线程池测试

    /**
     * 创建线程池测试方法
     */
    private static void createThread() {
        // 定义核心线程活跃数
        int corePoolSize = 2;
        // 定义最大线程活跃数
        int maximumPoolSize = 4;
        // 定义等待时间
        long keepAliveTime = 10;
        // 定义等待时间单位
        TimeUnit unit = TimeUnit.SECONDS;
        // 定义使用队列
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
        // 定义线程工厂
        ThreadFactory threadFactory = (Runnable r) -> {
            Thread t = new Thread(r, "my-thread-" + CreateThreadByThreadPoolExecutor.mThreadNum.getAndIncrement());
            System.out.println(t.getName() + " has been created");
            return t;
        };
        // 定义拒绝处理任务时的策略
        RejectedExecutionHandler handler = (Runnable r, ThreadPoolExecutor e) -> {
            // 做日志持久化记录
            System.err.printf("%s is rejected \n", r.toString());
        };
        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                keepAliveTime, unit, workQueue, threadFactory, handler);
        executor.prestartAllCoreThreads(); // 预启动所有核心线程

        for (int i = 1; i <= 10; i++) {
            executor.execute(() -> {
                System.out.printf("%s is running \n", Thread.currentThread().getName());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    log.error(e.getMessage());
                }
            });
        }
    }

结果如下 :

  • 线程1-4先占满了核心线程和最大线程数量
  • 线程5-6进入等待队列
  • 线程7-10被直接忽略拒绝执行
  • 等1-4线程中有线程执行完后通知4、5线程继续执行
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Aloneii

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

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

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

打赏作者

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

抵扣说明:

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

余额充值