Android 线程池解析与复用原理

作为面试的常住嘉宾之一,线程池的拷问,估计每个面试官都想问一遍。

下面,我们一起来学习一下。

一、线程池

首先,提到线程池就得说说它的好处,总得来说,可以分为以下三点:

  1. 复用线程池的线程,避免线程创建和销毁带来的性能开销。
  2. 控制线程池的最大并发数,避免大量线程之间抢占系统资源而导致的阻塞现象
  3. 能够对线程进行简单的管理,并提供定时执行以及制定间隔循环执行等任务

但需要注意的一点,如果只有一个线程,且不需要复用,则不需要用到线程池,没必要。

我们都知道,Java的线程池共有4中,newFixedThreadPool,newCachedThreadPool,newScheduledThreadPool,newSingleThreadExecutor 。但真正的线程池的实现为 ThreadPoolExecutor ,它提供了一系列参数来配置线程池,上述4中,其实就是不同参数的 ThreadPoolExecutor 而已。

二、ThreadPoolExecutor

接着,我们看看 ThreadPoolExecutor 的构造参数:

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

接着看看各个参数是什么意思:

  • corePoolSize:核心线程数
  • maximumPoolSize:线程池的最大线程数
  • keepAliveTime:非核心线程最大空闲存活时间(如果allowCoreThreadTimeOut = true,这个时间对核心线程也适用)
  • unit:时间单位,有秒,分
  • workQueue:线程池的任务等待队列
  • threadFactory :线程工程,在创建线程时,可以标准名字,方便调试
  • handler:拒绝策略,当线程池无法处理任务时的拒绝方式

2.1 线程池的中线程的增加策略

那么,从上面已经知道,线程的增加策略,跟三个参数有关:

  • corPoolSize:核心线程数
  • maximumPoolSize:线程池的最大线程数
    • workQueue:线程池的任务等待队列

他们之前的关系是这样的,图片来源
在这里插入图片描述

ThreadPoolExecutor 执行任务时,大致遵循以下规则:

  • 如果线程池中的线程数据未达到核心线程的数量,那么会直接启动一个核心线程来执行任务
  • 如果线程池中的线程已经达到了核心线程的数量,那么任务会被插入到任务队列中排队,等待执行
  • 如果队列已满,任务无法插入队列中,如果此时线程数量未达到最大线程数,那么会立刻启动一个非核心线程来执行这个任务。
  • 如果队列已满,且线程数已经达到最大线程数,则拒绝执行此任务,并调用 RejectedExecutionHandler 的 rejectedExecution 方法来调动调用发。

具体可以看下图:图片来源

在这里插入图片描述

这里需要注意的点是,这个队列需要是有界队列的;否则线程数量就不会增加到最大线程数,因为任务一直往队列增加,而队列一直未满。

三、复用原理

上面理解了 ThreadPoolExecutor 的工作原理,那么线程池的复用又是怎么回事呢?

首先,当我们使用 ThreadPoolExecutor execute 一个任务时,看看它的源码:

在这里插入图片描述
可以看到,这里有个 addWorker 的方法,把 runnable 添加进去了,跟踪进去:
在这里插入图片描述
看到,把 runnable 又传递给我了 Worker 这个实例,进去再看一下:
在这里插入图片描述
原来在 Worker 这里新建了一个 thread,并任务赋值给 firstTask ,那么这个 run 什么时候调用呢,回到 addWorker 方法:
在这里插入图片描述
发现,最后执行了 worker 的 start 方法,那么 worker 的run 方法也就执行了,你可以发现,其实你传进来的runable 并没有执行,而是执行 worker 自身的 runnable。
继续看 run 方法中的 runWorker():
在这里插入图片描述
发现它里面是一个 while 循环,当此时 task 为空的时候,则使用 getTask() 去拿:
在这里插入图片描述
拿到 task 之后,就会执行 task.run ,这样 runnable 就被复用了。

也可以这样理解, task 的 thread 复用了任务的 runable ,thread 创建的那几个,但是 runnable 可以多个,这样就起到了复用的效果了。

四、线程池分类

上面介绍了 ThreadPoolExecutor 的使用和实现原理,下面介绍一下 Executors 中 4中常见的 线程池

4.1 FixedThreadPool

通过 Executors 的 newFixedThreadPool 创建,它是一种线程数固定的线程池,参数如下:

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

可以看到,核心线程数和最大线程数是一直的,然后空闲时间为0,队列则是无线大。
从这里看,线程一旦创建,就是核心线程,那么空闲的时候,并不会回收,除非释放;当任务超过核心线程,则放入到队列中,等待执行,直到线程空闲出来。

4.2 CachedThreadPool

通过 Executors 的 newCachedThreadPool 创建,它是线程数量不固定的线程池,构造方法如下:

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

可以看到,核心线程是0,最大线程数非常大,而时间设置为60s,也就是说,60s内,如果线程空闲了,就会被回收。注意到的是,如果有任务来了,不会被立即执行,如果需要等待线程创建,并从队列取出任务。它适合大量的耗时较少的任务,因为当60s到来时,如果任务还未被执行,则会中断任务。

4.3 ScheduledThreadPool

它的构造参数为:

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

看到,它的核心线程数是固定的,最大则是不固定的,适合做一些延时操作。

4.4 SingleThreadExecutor

它的构造参数为:

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

核心线程数和最大线程数都为1,确保所有任务都在这个线程中执行。

参考:
https://mp.weixin.qq.com/s/FOs7hiUk7_W2GoZsM1Tc_w

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章