线程池,及7大参数,4大拒绝策略详解

线程池,及7大参数,4大拒绝策略详解

一、线程池

  1. 单独创建线程的弊端

    • 每次创建线程的时候都会占用一定的内存空间,如果不断地进行创建线程,可能会导致消耗大量的内存,导致OOM(Out Of Memory内存溢出)。
    • 一台电脑的cpu是有限的,同一时刻每个cpu只能处理一个线程,如果大量的请求到来,我们创建了大量的线程,那么很多线程都没有cpu的执行权,就需要等待cpu调度,这将导致大量线程的切换,也会导致性能变慢。

    所以说,一般在项目开发的过程中,都会使用线程池来管理线程。

  2. 线程池介绍

    线程池是一种利用池化技术思想来实现的线程管理技术,旨在复用已创建的线程、方便地管理线程和任务,并将线程的创建和任务的执行解耦。通过线程池,可以降低频繁创建和销毁线程所带来的资源消耗,提高系统的性能和资源利用率。

    在Java中,主要使用ThreadPoolExecutor类来创建线程池,同时JDK也提供了Executors工厂类来创建线程池。然而,对于高级别的应用,推荐直接使用ThreadPoolExecutor类进行线程池的创建和配置,以便更好地满足实际需求并避免一些潜在的问题。

二、线程池的核心参数——7大参数

查看源码,7大参数如下

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  1. corePoolSize(核心线程数目):线程池中同时存在的最小线程数,即使线程处于空闲状态,也不会被回收
  2. maximumPoolSize(最大线程数):线程池中允许存在的最大线程数,当队列满时并且当前线程数小于最大线程数时,线程池会创建新的线程(救急线程)来处理任务。最大线程数=核心线程数+救急线程数的最大值。
  3. keepAliveTime(生存时间):当线程池中的线程数量大于核心线程数时,多余的空闲线程(救急线程)在空闲时间超过keepAliveTime后会被回收。
  4. unit(时间单位):用于设置keepAliveTime的时间单位,例如TimeUnit.SECONDS。
  5. workQueue(工作队列):用于存储等待执行的任务的阻塞队列,当没有空闲核心线程时,新来任务会加入到此队列排队,队列满会创建新线程执行任务
  6. threadFactory(线程工厂):用于创建新线程的工厂,可以定制线程对象的创建,例如设置线程名字、是否是守护线程等
  7. handler(拒绝策略):当所有线程都在繁忙,workQueue也放满时,会触发拒绝策略

三、线程池的执行原理

  1. 当我们提交任务时,首先会判断核心线程是否已满,没满就添加到工作线程并执行,满了执行下一步。
  2. 判断阻塞队列是否已满,没满就添加到阻塞队列里进行等待,满了执行下一步。
  3. 判断线程数是否小于最大线程数,是就创建救急线程去执行这个任务,否就会进行拒绝策略的处理。当然这里面还有一种情况,就是当核心线程数和临时线程执行完任务处于空闲的时候,也会去检查一下,看阻塞队列中是否还有等待的任务,有的话就会使用核心线程或救急线程去执行阻塞队列中等待的任务。

image-20231205200753918

四、4大拒绝策略

  1. AbortPolicy(默认):当任务添加到线程池中被拒绝时,会抛出RejectedExecutionException异常。
  2. CallerRunsPolicy:当任务添加到线程池中被拒绝时,会使用调用者所在的线程来执行该任务。
  3. DiscardOldestPolicy:当任务添加到线程池中被拒绝时,会丢弃队列中最靠前(来的最久)的任务,然后尝试重新提交被拒绝的任务。
  4. DiscardPolicy:当任务添加到线程池中被拒绝时,会丢弃该任务,不会有任何异常抛出。

五、代码展示执行原理

定义一个静态内部类MyTask,用来创建提交的任务

public class TestThreadPoolExecutor {

    static class MyTask implements Runnable {

        private final String name;
        private final long duration;

        public MyTask(String name) {
            this(name, 0);
        }

        public MyTask(String name, long duration) {
            this.name = name;
            this.duration = duration;
        }

        @Override
        public void run() {
            try {
                System.out.println("【" + Thread.currentThread().getName() + "】 running..." + this);
                Thread.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
            return "MyTask(" + name + ")";
        }
    }
}

定义一个方法用来输出当前线程数,阻塞队列的任务情况

private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) throws InterruptedException {
        Thread.sleep(50);
        System.out.print("【" + Thread.currentThread().getName() + "】 pool size:" + threadPool.getPoolSize());
        System.out.print(",queue:");
        queue.forEach(t -> {
            System.out.print(t + " ");
        });
        System.out.println();
}

在main方法中定义一个线程池(核心线程数为2,最大线程数为3,空闲线程存活时间为0,阻塞队列大小为2,使用AbortPolicy这种抛异常的拒绝策略)

public static void main(String[] args) throws InterruptedException {
        AtomicInteger c = new AtomicInteger(1);
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                queue,
                r -> new Thread(r, "myThread" + c.getAndIncrement()),
                new ThreadPoolExecutor.AbortPolicy()
        );
        showState(queue, myThreadPool);
}

执行main,线程池的初始状态如下

image-20231205233326427

提交4个任务,第二个参数写那么大是为了模拟任务1和2一直处理执行的状态

showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("1", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("2", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("3"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("4"));
showState(queue, myThreadPool);

结果如下,2个核心线程在执行任务1和2,而任务3和4则在阻塞队列中

image-20231205233553602

此时再提交一个任务5

showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("1", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("2", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("3"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("4"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("5", 3600000));
showState(queue, myThreadPool);

结果如下,因为还没达到最大线程数,将会创建救急线程执行任务5

image-20231205234053098

而如果将任务5的时间改为0,则救急线程执行完任务5后就里面执行阻塞队列里的任务3和任务4

myThreadPool.execute(new MyTask("5", 0));
showState(queue, myThreadPool);

image-20231205234259604

现在,我们把任务5时间改回来(一直执行),提交一个任务6

showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("1", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("2", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("3"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("4"));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("5", 3600000));
showState(queue, myThreadPool);
myThreadPool.execute(new MyTask("6"));
showState(queue, myThreadPool);

结果如下,会触发拒绝策略,我们使用的拒绝策略为AbortPolicy,所以会抛出异常

image-20231205234655062

我们再试试其他的,CallerRunsPolicy,使用的是调用者所在的线程所在的main线程

image-20231205234939290

DiscardOldestPolicy,把队列里来的最久的任务3丢掉了

image-20231205235202502

DiscardPolicy,直接拒绝任务6

image-20231205235258432

以下附完整代码

public class TestThreadPoolExecutor {

    static class MyTask implements Runnable {

        private final String name;
        private final long duration;

        public MyTask(String name) {
            this(name, 0);
        }

        public MyTask(String name, long duration) {
            this.name = name;
            this.duration = duration;
        }

        @Override
        public void run() {
            try {
                System.out.println("【" + Thread.currentThread().getName() + "】 running..." + this);
                Thread.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
            return "MyTask(" + name + ")";
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger c = new AtomicInteger(1);
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor myThreadPool = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                queue,
                r -> new Thread(r, "myThread" + c.getAndIncrement()),
                new ThreadPoolExecutor.DiscardPolicy()
        );
        showState(queue, myThreadPool);
        myThreadPool.execute(new MyTask("1", 3600000));
        showState(queue, myThreadPool);
        myThreadPool.execute(new MyTask("2", 3600000));
        showState(queue, myThreadPool);
        myThreadPool.execute(new MyTask("3"));
        showState(queue, myThreadPool);
        myThreadPool.execute(new MyTask("4"));
        showState(queue, myThreadPool);
        myThreadPool.execute(new MyTask("5", 3600000));
        showState(queue, myThreadPool);
        myThreadPool.execute(new MyTask("6"));
        showState(queue, myThreadPool);
    }

    private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) throws InterruptedException {
        Thread.sleep(50);
        System.out.print("【" + Thread.currentThread().getName() + "】 pool size:" + threadPool.getPoolSize());
        System.out.print(",queue:");
        queue.forEach(t -> {
            System.out.print(t + " ");
        });
        System.out.println();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值