任务执行服务的主要实现机制:线程池

定义

线程池,就是一个线程的池子,里面有若干线程,它们的目的就是执行提交给线程池的任务,执行完一个任务后不会退出,而是继续等待或执行新任务。

线程池主要是由两个概念组成:一个是任务队列;另一个是工作者线程。工作者线程主体就是一个循环,循环从队列中接受任务并执行,任务队列保存待执行的任务。

new Thread 的弊端

  • 每次new Thread 新建对象,性能差
  • 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或OOM
  • 缺少更多功能,如更多执行,定期执行,线程中断

线程池的好处

  • 重用存在的线程,减少对象创建。消亡的开销,性能佳
  • 可有效控制最大并发线程数,提高系统资源利用率,同时可以避免过多资源竞争,避免阻塞。
  • 提供定时执行,定期执行,单线程,并发数控制等功能。

理解线程池的一些参数

ThreadPoolExecutor

  • corePoolSize:核心线程数量,需要注意的是在初创建线程池时线程不会立即启动,直到有任务提交才开始启动线程并逐渐时线程数目达到corePoolSize。若想一开始就创建所有核心线程需调用prestartAllCoreThreads方法。
  • maximunPoolSize:线程最大线程数,。需要注意的是当核心线程满且阻塞队列也满时才会判断当前线程数是否小于最大线程数,并决定是否创建新线程。
  • workQueue:阻塞队列。存储等待执行的任务。

1、如果当前线程池的线程数还没有达到基本大小(poolSize < corePoolSize),无论是否有空闲的线程新增一个线程处理新提交的任务;

2、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSize) 且任务队列未满时,就将新提交的任务提交到阻塞队列排队,等候处理;

3、如果当前线程池的线程数大于或等于基本大小(poolSize >= corePoolSize) 且任务队列满时; (1)当前poolSize<maximumPoolSize,那么就新增线程来处理任务; (2)当前poolSize=maximumPoolSize,那么意味着线程池的处理能力已经达到了极限,此时需要执行拒绝策略(handler)

  • keepAliveTime:线程没有任务执行时最多保持多久时间终止
  • unit:keepAliveTime的时间单位
  • threadFactory:线程工厂,用来创建线程
  • rejectHandler:当拒绝处理任务时的策略

workQueue

无界队列

队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列做为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM

有界队列

常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。

使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量

同步移交队列

使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量

主要的构造方法

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

线程池大小

maximumPoolSize 表示线程池的最多线程数,线程的个数会动态变化,但这是最大值,不管有多少任务,都不会创建比这个值最大的线程个数。

corePoolSize 表示线程池中的核心线程个数,不过,并不是一开始就创建这么多线程,刚创建一个线程池后,实际上并不会创建任何线程。

一般情况下,有新任务到来的时候,如果当前线程个数小于corePoolSize,就会创建一个新线程来执行该任务,需要说明的是,即使其他线程现在也是空闲的,也会创建新的线程。不过,如果线程个数大于等于corePoolsize,那就不会立即创建新线程了,它会先尝试排队,需要强调的是,它是“尝试排队”,而不是“阻塞等待” 入队,如果队列满了或其他原因不能立即入队,它就不会排队,而是检查线程的个数是否达到了maximumPoolSize ,如果没有,就会继续创建线程,直到线程数达到maximumPoolSize.

keepAliveTime 的目的是为了释放多余的线程资源,它表示,当线程池中的线程个数大于corePoolSize时,额外空闲线程的存活时间。也就是说,一个非核心线程,在空闲等待新任务时,会有一个最长等待时间,即keepAliveTime,如果到了时间还是没有新任务,就会被终止。如果该值为0,则表示所有线程都不会超时终止。

任务拒绝策略

如果队列有界,且maximumPoolSize 有限,则当队列排满,线程个数也达到了了maximumPoolSize,这时,新任务来了,如何处理呢?此时,会触发线程池的任务拒绝策略。

提交任务的方法(execute/submit/invokeAll)会抛出异常,类型为RejectedException。

ThreadPoolExecutor实现了四种处理方式:

(1)ThreadPoolExecutor.AbortPolicy:这就是默认的方式没跑出异常。

(2)ThreadPoolExecutor.DiscardPolicy:静默处理,忽略新任务,不抛出异常,也不执行。

(3)ThreadPoolExecutor.DiscardOldestPolicy: 将等待时间最长的任务扔掉,然后自己排队。

(4)ThreadPoolExecutor.CallerRunsPolicy:在任务提交者线程中执行任务,而不是交给线程池中线程执行。

它们都是ThreadPoolExecutor 的public静态内部类,都实现了RejectedExecutionHandler接口,这个接口的定义为:

public interface RejectedExecutionHandler {

    /**
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     * task.  This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>In the absence of other alternatives, the method may throw
     * an unchecked {@link RejectedExecutionException}, which will be
     * propagated to the caller of {@code execute}.
     *
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

默认的RejectedExecutionHandler是一个AbortPolicy 实例。如下:

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();
 /**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    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());
        }
    }

拒绝策略只有在队列有界,且maximumPoolSize 有限的情况下才会触发。如果队列无界,服务不了的任务总会排队,但这不一定是期望的结果,因为请求处理队列可能会消耗非常大的内存,甚至引发内存不够的异常,如果队列有界,但maximumPoolSize 无限,可能会创建过多的线程,占满CPU和内存,使得任何任务都难以完成。所以,在任务量非常大的场景中,让拒绝策略有机会执行是保证系统稳定运行很重要的方面。

线程池的状态

image-20210310203924877

  • RUNNING 状态:能接收新提交的任务,同时也能处理阻塞队列中的任务。
  • SHUTDOWN 状态:不能接受新提交的任务,但是可以处理阻塞队列中的任务。
  • STOP 状态:不接受新的任务,也不处理队列中的任务。
  • TIDYING 状态:
  • TERMINATED 状态

常用方法

execute(): 提交任务,交给线程池执行。

submit(): 提交任务,能够返回执行结果 execute+Future。

shutdown():关闭线程池,等到任务都执行完。

shutdownNow(): 关闭线程池,不等待任务执行完。

getTaskCount(): 线程池已执行和未执行的任务总数。

getCompletedTaskCount(): 已完成的任务数量。

getPoolSize():线程池当前的线程数量。

getActiveCount(): 当前线程池中正在执行任务的线程数量。

类图

image-20210310205901141

Executor 框架接口

Executors.newCachedThreadPool : 创建一个可缓存的线程池,如果线程的数量超过处理的需要,可以灵活回收空闲的线程,若没有回收的,就新建线程。

Executors.newFixedThreadPool:创建一个定长的线程池,可以控制线程的并发数,超出的线程需要在阻塞队列中等待。

Executors.newScheduledThreadPool: 创建一个定长的线程池,支持周期,定时性的任务执行。

Executors.newSingleThreadExecutor:创建一个单线程化的线程,只会用唯一的一个工作线程去处理任务。保证所有任务按照指定的顺序去执行。

合理配置

CPU密集型任务,就需要尽量压榨CPU,参考值可以设为NCPU+1

IO 密集型任务,参考值可以设置为2*NCPU

线程工厂

线程池还可以接受一个参数,ThreadFactory。它还是一个接口,定义为:

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

根据Runnable 创建一个Thread。 ThreadPoolExecutor的默认实现是Executors类中的静态内部类DefaultThreadFactory,主要就是创建一个线程,给线程设置一个名称,设置daemon属性为false,设置线程优先级为标准默认优先级,线程名称的格式为:pool-<线程池编号>-thread-<线程编号>。如果需要自定义一些线程的属性,比如名称,可以实现自定义的ThreadFactory。

static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }

核心线程的特殊配置:

线程个数小于等于corePoolSize时,我们称这些线程为核心线程,默认情况下:

(1) 核心线程不会预先创建,只有当有任务时,才会创建。

(2)核心线程不会因为空闲而被终止,keepAliveTime 参数不适用于它。

//预先创建所有线程
public int prestartAllCoreThreads() 

// 创建一个核心线程,如果所有核心线程都已创建,则返回false
public boolean prestartCoreThread() 

// 如果参数为true,则keepAliveTime 参数也适用于核心线程
public void allowCoreThreadTimeOut(boolean value)

工厂类Executors

提供了一些静态的工厂方法,可以方便地预创建一些线程池

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

只使用一个线程,使用无界队列LinkedBlockingQueue,线程创建后不会超时终止,该线程顺序执行所有任务。该线程池适用于需要确保所有任务被顺序执行的场合。

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

使用固定数目的n个线程,使用无界队列LinkedBlockingQueue,线程创建后,不会超时终止。和newSingleThreadExecutor一样,由于是无界队列,如果排队任务过多,可能会消耗过多的内存。

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

它的corePoolSize为 0 ,maximumPoolSize 为Integer.Max_Value, keepAliveTime 是60秒,队列为SynchronousQueue。它的含义是:当新任务到来时,如果正好有空闲线程在等待任务,则其中一个空闲线程接受该任务,否则就总是创建一个新线程,创建的总线程个数不受限制,对任一空闲线程,如果60秒内没有新任务,就终止。

实际中,应该使用newFixedThreadPool 还是newCachedThreadPool呢?

在系统负载很高的情况下,newFixedThreadPool 可以通过队列对新任务排队,保证有足够的资源处理实际的任务,而newCachedThreadPool 会为每个任务创建一个线程,导致创建过多的线程竞争CPU和内存资源,使得任何实际任务都难以完成,这时,newFixedThreadPool 更为适用。

不过,如果系统负载不是太高,单个任务的执行时间也比较短,newCachedThreadPool 的效率可能更高,因为任务可以不经排队,直接交给某一个空线程。

在系统负载可能极高的情况下,两者都不是好的选择,newFixedThreadPool的问题是队列过长,而newCachedThreadPool的问题是线程过多,这时,应根据具体情况自定义ThreadPoolExecutor,传递合适的参数。

线程池的死锁

任务之间有依赖,这种情况可能会出现死锁。比如任务A,在它的执行过程中,它给同样的任务执行服务提交了一个任务B,但需要等待任务B结束。

如果任务A是提交了一个单线程池,一定会出现死锁,A在等待B的结果,而B在队列中等待调度。如果是提交给了一个限定线程个数的线程池,也能因线程数限制出现死锁。

怎么解决这种问题呢?可以使用newCachedThreadPool创建线程池,让线程数不受限制。另一个解决方法是使用SynchronousQueue,它可以避免死锁,怎么做到的呢?对于普通队列,入队只是把任务放到了队列中,而对于SynchronousQueue来说,入队成功就意味着已有线程接受处理,如果入队失败,可以创建更多线程直到maximumPoolSize,如果达到了maximumPoolSize,会触发拒绝机制,不管怎么样,都不会死锁。

注意

Alibaba命名规范的解释:

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明: Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool : 允许的请求队列长度为 Integer.MAX_VALUE ,可能会堆积大量的请求,从而导致 OOM 。

2) CachedThreadPool 和 ScheduledThreadPool : 允许的创建线程数量为 Integer.MAX_VALUE ,可能会创建大量的线程,从而导致 OOM 。

参考文章

java编程的逻辑基础(马俊昌)
线程池中的阻塞队列选择 (强烈推荐,写的很详细)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

涛涛之海

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

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

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

打赏作者

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

抵扣说明:

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

余额充值