攻城掠地 —— 线程池

549 阅读6分钟

1. 线程池的使用

       // 1. 创建线程池
        ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1,
                0,
                60,
                TimeUnit.HOURS,
                new ArrayBlockingQueue<>(1, true));

        // 2. 线程池提交任务
        // 2.1 execute方式
        poolExecutor.execute(new Runnable() {
            @Override
            public void run() {

            }
        });
        // 2.2 submit方式
        Future<?> submit = poolExecutor.submit(new Runnable() {
            @Override
            public void run() {

            }
        });

        //3. 关闭线程池
        poolExecutor.shutdown();
        poolExecutor.shutdownNow();

1.1 线程池的创建参数解析

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

    • 线程池的核心线程的大小
    • 提交一个任务到线程池中,都会创建一个新的线程去执行任务,即使有空闲的线程,直到达到线程池的的核心线程数
    • 调用prestartAllCoreThreads()方法,线程池会提前创建并启动所有的基本线程
  • BlockingQueue

    用于保存等待执行任务的阻塞队列

    • ArrayBlockingQueue:基于数据结构的有界阻塞队列;按FIFO(先进先出)对元素进行排序
    • LinkedBlockingQueue:基于链表结构的阻塞队列;按FIFO排序元素,吞吐量高于ArrayBlockingQueue
    • SynchronousQueue:不存储元素的阻塞队列;每次插入操作必须等待另一个线程代用移除操作,否则插入操作一直处于阻塞状态;吞吐量高于LinkedBlockingQueue
    • PriorityBlockingQueue:具有优先级的无限阻塞队列
  • ThreadFactory

    设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程相关的属性

  • RejectedExecutionHandler (rejectedExecution()方法处理)

    饱和策略:当队列和线程池满后,线程池处于饱和状态,需要采用一个策略来处理新提交的任务

    • AbortPolicy:直接抛出异常 (默认)
       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                throw new RejectedExecutionException("Task " + r.toString() +
                                                     " rejected from " +
                                                     e.toString());
            }
    
    • CallerRunsPolicy:如果添加到线程池失败,会在调用者的主线程执行改任务
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                if (!e.isShutdown()) {
                    r.run();
                }
            }
    
    • DiscardOldestPolicy:移除对头的任务元素,再尝试入队
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                if (!e.isShutdown()) {
                    e.getQueue().poll();
                    e.execute(r);
                }
            }
        }
    
    • DiscardPolicy:如果线程池队列满了直接丢掉此任务(方法实现为空方法)
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            }
    
  • maximumPoolsize

    线程池允许创建的最大线程数

  • keepAliveTime

    线程池的工作线程空闲后,保持存活的时间

  • TimeUnit

    线程活动保持的时间单位

1.2 线程池提交任务

1.2.1 execute()提交任务

用于提交不需要返回的任务,无法判断任务是否被线程池执行成功;

        poolExecutor.execute(new Runnable() {
            @Override
            public void run() {

            }
        });
1.2.2 submit()提交任务

submit()提交需要返回值的任务。可以通过返回值Future对象可以判断任务是否执行成功

        Future<?> submit = poolExecutor.submit(() -> { });
        try {
            submit.get();
        } catch (InterruptedException e) {
            // 处理中断的异常
            e.printStackTrace();
        } catch (ExecutionException e) {
            // 处理无法执行任务的异常
            e.printStackTrace();
        } finally { // 关闭线程池
            poolExecutor.shutdown();
        }

1.3 关闭线程池

通过调用shutdown()或者shutdownNow方法来关闭线程池,都是通过遍历线程池中的工作线程,然后逐个调用线程中的interrupt方法来中断线程,但是无法响应中断任务的可能无法终止

区别:

  • shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有的正在执行或者暂停任务的线程,并返回等待执行任务的列表
  • shutdown只是将线程池的状体设置为shutDown状态,中断所有没有正在执行任务的线程

2. 线程池的种类

利用Executors类提供了四种不同的线程:newCachedThreadPool、newFixedThreadPool、newScheduledThreadPool、newSingleThreadExecutor

2.1 newCacheThreadPool

创建一个可缓存的无界线程池,当线程池中的线程空闲时间超过60s,则会自动回收改线程,当任务超过线程池的线程数则创建新的线程。线程池的上限为Integer.MAX_VLIUE。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

使用方法:

        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                
            }
        });

2.2 newFixedThreadPool

创建一个固定大小的线程池,可指定线程池的固定大小,对于超出的线程在LinkedBlockingQueue队列中等待

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

使用方法:

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        fixedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                
                
            }
        });

2.3 newSingleThreadExecutor

创建只有一个线程的线程池,所有任务都保存在队列LinkedBlockingQueue中,等待唯一的单线程来执行任务,保证所有的任务按照指定顺序(FIFO或者优先级)执行

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

使用方法:

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(new Runnable() {
            @Override
            public void run() {
                
            }
        });

2.4 newScheduledThreadPool

创建一个可定时执行或周期执行任务的线程池,可指定线程池的核心线程个数

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
   

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

使用方法:

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        // 定时执行一次任务,延迟1S后执行
        scheduledExecutorService.schedule((Runnable) () -> {

        }, 1, TimeUnit.SECONDS);
        // 周期性执行任务,延迟2s,每3S一次周期性执行任务
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {

            }
        }, 2, 3, TimeUnit.SECONDS);
  • schedule(Runnable command, long delay,TimeUnit unit),延迟一定时间后执行Runnable任务
  • schedule(Callable callable, long delay, TimeUnit unit),延迟一定时间后执行Callable任务
  • scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),延迟一定时间后,以间隔period时间的频率周期性地执行任务
  • scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit),与scheduleAtFixedRate()方法很类似,但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔

2.5 各种线程池的比较

方法corePoolSizemaxmumPoolSizekeepAliveTimeworkQueuethreadFactoryhandler
newCacheThreadPool0Integer.MaX_VAlie60SSynchronousQueueDefaultThreadFactoryThreadPoolExecutor.AbortPolicy
newFixedThradPoolnThreadnThread0LinkedBlokcingQueueSynchronousQueueDefaultThreadFactoryThreadPoolExecutor.AbortPolicy
newSingleThreadExecutor110LinkedBlockingQueueSynchronousQueueDefaultThreadFactoryThreadPoolExecutor.AbortPolicy
newScheduledThreadPoolcorePoolSizeInteger.Max_VALUE-DelayedWorkQueueSynchronousQueueDefaultThreadFactoryThreadPoolExecutor.AbortPolicy

3. 线程池的流程

image.png

4 . 优化

4.1 配置线程池

  • 任务的性质

    • CPU密集型:使用多配置maximumPoolSize,当前CPU数目(Runtime.getRuntime().availableProcessors())的的corePoolSize
    • IO密集型,多corePoolSize
  • 任务的优先级

    使用优先级队列:PriorityBlockingQueue

  • 任务的执行时间

4.2 线程池的监控

通过继承线程池来自定义线程池,重写 线程池beforeExecute、afterExecute、terminated来任务执行前、执行后和线程池关闭执行一些代码来进行监控。

  • taskCount:线程池需要执行的任务数量。
  • completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount。
  • largestPoolSize:线程池曾经创建过的最大线程数量,通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
  • getActiveCount:获取活动的线程数。

5. 优点

  • 降低系统资源消耗,通过重用线程,降低线程的创建和销毁造成的消耗
  • 提高系统响应速度,任务达到后,无需等待新线程的创建立即执行
  • 方便线程并发数的管控,线程如果无限制创建,会消耗大量系统资源,占用过多资源而阻塞系统或oom,降低系统的稳定性,线程池能有效管控线程,统一分配、调优、提供资源使用率
  • 线程池提供定时、定期、可控线程数等功能的线程池