Java线程池解读

线程池的饱和策略与阻塞队列

一、线程池的饱和状态

1、线程池的处理过程

提交一个任务到线程池中,线程池的处理流程如下:

  • 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。
  • 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。
  • 判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

这里写图片描述

2、饱和状态的满足条件

  • 任务队列满了
    • 包括SynchronousQueue队列的特殊情况,由于它是无缓冲的,所以可以看成非常容易满。这个时候如果达到maxSize,就会触发。
  • 线程池满了
    • 对于linkedBlockingQueue是不会触发的。

二、饱和策略

饱和策略是针对线程池出现饱和情况的处理,通常有四种:

AbortPolicy

  • 直接抛出异常拒绝。
public static class AbortPolicy implements RejectedExecutionHandler {
    /**
     * Creates an {@code AbortPolicy}.
     */
    public AbortPolicy() { }

    /**
     * Always throws RejectedExecutionException.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     * @throws RejectedExecutionException always
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

CallerRunsPolicy

  • 调用 调用线程执行,(调用线程即 那个调用pool.exe…的线程)
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code CallerRunsPolicy}.
     */
    public CallerRunsPolicy() { }

    /**
     * Executes task r in the caller's thread, unless the executor
     * has been shut down, in which case the task is discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

DiscardOldestPolicy

  • 丢弃队列最近一个任务,执行当前任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardOldestPolicy} for the given executor.
     */
    public DiscardOldestPolicy() { }

    /**
     * Obtains and ignores the next task that the executor
     * would otherwise execute, if one is immediately available,
     * and then retries execution of task r, unless the executor
     * is shut down, in which case task r is instead discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

DiscardPolicy

  • 直接丢弃,会造成丢失。
public static class DiscardPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardPolicy}.
     */
    public DiscardPolicy() { }

    /**
     * Does nothing, which has the effect of discarding task r.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

以上就是四种策略,当然,我们通常会自定义自己的策略,如下:

//设置饱和策略
((ThreadPoolExecutor) manualPool).setRejectedExecutionHandler((r, executor) -> {
    //(1)use executor retry
    //but may occurred the stackOverflow
    //executor.execute(r);
    //(2)
    //use current thread run it
   // r.run();
});

三、阻塞队列

在线程池的执行任务过程中,会用到任务队列,work线程就会从队列中poll()出任务进行执行。队列可以分为两种,一种是有界,一种是无界,两种有很大区别,下面进入讲解。

1、有界队列

ArrayBlockingQueue

这是一个有界的阻塞队列,可以指定队列的大小,即任务的数量。具体的处理情况如下:

  • 核心池未满,创建线程进行执行任务
  • 队列未满,任务数超过核心线程数
    • 任务加入队列,等待执行。
  • 队列满了,任务数超过核心线程数
    • 开辟maxSize-coreSize个线程去执行任务(这部分线程具有keepAlive
    • 如果线程数达到了maxSize,这个时候任务队列也满,那么,就会执行饱和策略

2、无界队列

LinkedBlockingQueue

这是基于链表结构的阻塞队列,因此是无界的,该队列受限于核心线程数,与池外线程无关。使用时务必注意。

  • 吞吐量比数组阻塞队列高,延时也比较低,主要受限于核心线程数。
  • maxSize-coreSize的池外线程数与它无关,因为它只会调用核心池进行处理。
  • 延时较低

SynchronousQueue

这是无缓冲的阻塞队列,无缓冲,意味着每个任务入队必须等待一个任务出队,否则阻塞。吞吐量通常要高于链表队列。但是以下是一些需要注意的地方:

  • maxSize需要较大,否则容易触发饱和策略。
    • 因为当任务量多,在第一个任务出队之前,其它任务都进不了队,就会创建池外线程进行任务的执行。这个时候如果池外线程允许的数量太小,就会触发饱和策略。
  • keepAlive与池外线程仍然相关。
  • 当maxSize够大,弹性吞吐量也能够很高,当然创建池外线程的消耗也会增大,但是从某种意义上讲,能够保证的吞吐量是足够大的。
  • 延时相对较高。

demo

public class ThreadPoolUsage {
    static class DefaultThreadFactory implements ThreadFactory {
        /**
         * int type can support the thread nums.
         */
        private AtomicInteger threadNo = new AtomicInteger(1);
        private final String nameStart;
        private final String nameEnd = "]";
        private final boolean isDaemon;

        public DefaultThreadFactory(String poolName, boolean isDaemon) {
            this.isDaemon = isDaemon;
            this.nameStart = "[" + poolName + "-";
        }

        @Override
        public Thread newThread(Runnable r) {
            String threadName = this.nameStart + this.threadNo.getAndIncrement() + nameEnd;
            Thread newThread = new Thread(r, threadName);
            //set  daemon
            newThread.setDaemon(isDaemon);
            if (newThread.getPriority() != 5) {
                newThread.setPriority(5);
            }
            return newThread;
        }
    }

    public static void main(String[] args) {
        //手动创建
        //coreSize核心池的线程数,这些线程在创建后不会被回收,除非关闭池
        //maxSize=coreSize+restSize;
        //restSize的线程会有keepAlive参数,当线程idle keepAliveTime,就会被回收
        //timeUnit 即keepAlive的时间单位
        ExecutorService manualPool = new ThreadPoolExecutor(8, Integer.MAX_VALUE, 60, TimeUnit.MINUTES,
                new LinkedBlockingDeque<>(), new DefaultThreadFactory("QG", false));
        //pre Start all core thread.
        ((ThreadPoolExecutor) manualPool).prestartAllCoreThreads();
        //设置饱和策略
        ((ThreadPoolExecutor) manualPool).setRejectedExecutionHandler((r, executor) -> {
            //(1)use executor retry
            //but may occurred the stackOverflow
            //executor.execute(r);
            //(2)
            //use current thread run it
            r.run();
        });
        long s=System.currentTimeMillis();
        for (int i = 0; i < 3000; i++) {
            manualPool.execute(() -> System.err.println(Thread.currentThread().getName()));
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("======="+(System.currentTimeMillis()-1000-s));
        //这是由线程池完成的任务数量
        System.err.println("numbers of tasks completed by pool workers:"+((ThreadPoolExecutor) manualPool).getCompletedTaskCount());
        System.err.println("latest pool max size:"+((ThreadPoolExecutor) manualPool).getLargestPoolSize());
        //gracefully shutdown.
       // List<Runnable> waitRunTasks = manualPool.shutdownNow();
    }
}
  • linkedBlockedQueue
    • 耗时5MS
  • SynchronousQueue
    • 耗时77MS

当然,就吞吐量而言,肯定是SynchronousQueue更大,但是延时更高。而且这个SynchronousQueue的maxSize最好设置更大一些,否则容易出现饱和。

发布了57 篇原创文章 · 获赞 32 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/rekingman/article/details/99079526
今日推荐