线程池

38 阅读4分钟

线程池

1、线程池的创建

线程池可以通过ThreadPoolExecutor来创建,我们来看一下它的构造函数

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,
   BlockingQueue<Runnable> workQueue,
   ThreadFactory threadFactory,
   RejectedExecutionHandler handler) 

几个核心参数的作用:

  • corePoolSize: 线程池核心线程数最大值
  • maximumPoolSize: 线程池最大线程数大小
  • keepAliveTime: 线程池中非核心线程空闲的存活时间大小
  • unit: 线程空闲存活时间单位
  • workQueue: 存放任务的阻塞队列
  • threadFactory: 用于设置创建线程的工厂,可以给创建的线程设置有意义的名字,可方便排查问题。
  • handler: 线城池的饱和策略事件,主要有四种类型。

2、任务执行

线程池执行流程,即对应execute()方法:

image.png

  • 提交一个任务,线程池里存活的核心线程数小于线程数corePoolSize时,线程池会创建一个核心线程去处理提交的任务。
  • 如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
  • 当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建一个非核心线程执行提交的任务。
  • 如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

3、线程池的工作队列

ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。

LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列

DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。

PriorityBlockingQueue(优先级队列)是具有优先级的无界阻塞队列;

SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。

4、四种拒绝策略

  • AbortPolicy(抛出一个异常,默认的)

            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
            for (int i = 0; i < 10; i++) {
                try {
                    threadPoolExecutor.submit(() -> {
                        try {
                            System.out.println(Thread.currentThread().getName());
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    });
                } catch (Exception e) {
                    if (e instanceof RejectedExecutionException) {
                        System.out.println("线程池满啦");
                    }
                }
            }
            threadPoolExecutor.shutdown();
        }
    

image.png

  • DiscardPolicy(直接丢弃任务)

image.png

image.png

  • DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)

  • CallerRunsPolicy(交给线程池调用所在的线程进行处理)

image.png

image.png

5、execute()方法和submit()

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
​
        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(() -> System.out.println("11"));//适合适用于Runnable
​
        Future<Integer> submit = service.submit(() -> 3);//适合使用于Callable 获取线程得执行结果
        Integer integer = submit.get();
        //3.关闭连接池
        service.shutdown();
    }

image.png

6、线程池满了怎么通知客户端

  • 第一种方案就是创建线程池得时候使用默认得饱和策略AbortPolicy,然后再任务提交得地方捕获异常,再catch块里面通知客户端。

    // 创建一个最大线程数为2的线程池
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 2,0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), 
    new ThreadPoolExecutor.AbortPolicy());
    // 同时提交10个任务
    for (int i = 0; i < 10; i++) {
        try {
            threadPoolExecutor.submit(() -> {
                try {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
        } catch (Exception e) {
            if (e instanceof RejectedExecutionException) {
                System.out.println("线程池满啦");
                // 通知客户端
            }
        }
    }
    ​
    threadPoolExecutor.shutdown();
    ​
    
  • 上述方式捕获异常之后,相当于时任务丢弃了,实际工作中这种方案肯定时不可取的,查阅相关资料好像没有好的解决方案,笔者在网上看到有重写饱和策略的,因为不能丢弃任务,因此可以参考CallerRunsPolicy策略的实现方案,丢给主线程执行,并通知到客户端,大家有啥想法也可以一起讨论。

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(1), Executors.defaultThreadFactory(), new ThreadPool.MyRejectedExecutionHandler());
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.submit(() -> {
                try {
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
        }
        threadPoolExecutor.shutdown();
    }
​
​
     static class MyRejectedExecutionHandler implements RejectedExecutionHandler {
         public MyRejectedExecutionHandler() { }
​
         public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
             if (!e.isShutdown()) {
                 r.run();
                 //通知客户端
                 System.out.println("线程池满了");
             }
         }
    }

image.png

参考

juejin.cn/post/684490…

juejin.cn/post/684490…