美文网首页
Okhttp原理之线程池

Okhttp原理之线程池

作者: 馒Care | 来源:发表于2021-07-02 14:22 被阅读0次

    线程的概念

    • 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

    线程的特点

    • 在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体

    线程的属性

    • 轻量级(线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述,线程调度速度相对较快)
    • 独立调度和分配(在多线程os中,线程是可以独立运行的基本单位)
    • 并发性执行(一个进程包含多个线程)
    • 共享进程资源 (一个进程包含多个线程)

    进程和线程都是一个时间段的描述,是 CPU 工作时间段的描述,只是颗粒不同罢了。

    线程池的概念

    • 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。

    线程池的优点

    1.降低内存消耗:通过复用线程,降低线程的创建以及销毁带来的损耗
    2.提高响应速度:当任务到达,无需等待线程创建可以立即执行,跟第一点类似
    3.提高管理型:由于线程占用系统资源,如果无限制的创建,不仅会消耗系统的资源,还会降低系统的稳定性,使用线程池可以进行统一的管理,如资源的分配、性能优化等。

    线程池的源码分析

    /**
         * Creates a new {@code ThreadPoolExecutor} with the given initial
         * parameters and default thread factory.
         *
         * @param corePoolSize the number of threads to keep in the pool, even
         *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
         * @param maximumPoolSize the maximum number of threads to allow in the
         *        pool
         * @param keepAliveTime when the number of threads is greater than
         *        the core, this is the maximum time that excess idle threads
         *        will wait for new tasks before terminating.
         * @param unit the time unit for the {@code keepAliveTime} argument
         * @param workQueue the queue to use for holding tasks before they are
         *        executed.  This queue will hold only the {@code Runnable}
         *        tasks submitted by the {@code execute} method.
         * @param handler the handler to use when execution is blocked
         *        because the thread bounds and queue capacities are reached
         * @throws IllegalArgumentException if one of the following holds:<br>
         *         {@code corePoolSize < 0}<br>
         *         {@code keepAliveTime < 0}<br>
         *         {@code maximumPoolSize <= 0}<br>
         *         {@code maximumPoolSize < corePoolSize}
         * @throws NullPointerException if {@code workQueue}
         *         or {@code handler} is null
         */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    

    1.int corePoolSize(核心线程数):线程池创建线程的时候,核心线程数将被保留在线程池中,即便当前核心线程数处于(闲置状态),除非设置了allowCoreThreadTimeOut为true,那么如果核心线程数处于(闲置状态),在超过一定时间(keepAliveTime),就会被销毁掉。

    2.int maximumPoolSize(线程池最大容量):线程池最大容量=核心线程数+非核心线程数

    3.long keepAliveTime (通指非核心线程闲置存活时间):非核心线程空闲时长超过一定时长keepAliveTime,将会被回收

    4.TimeUnit unit(keepAliveTime的时间单位)

    5.BlockingQueue<Runnable> workQueue(任务队列)

    • 当超过核心线程数的时候,新添加的任务会被添加到任务队列中处理。
    ThreadPoolExecutor.executor()
    
     if (workerCountOf(c) < corePoolSize) {
                if (addWorker(command, true))
                    return;
                c = ctl.get();
            }
            if (isRunning(c) && workQueue.offer(command)) {
                int recheck = ctl.get();
                if (! isRunning(recheck) && remove(command))
                    reject(command);
                else if (workerCountOf(recheck) == 0)
                    addWorker(null, false);
            }
            else if (!addWorker(command, false))
                reject(command);
    
    • 当队列已满,则新建非核心线程,注意,这里入参是false,代表使用非核心线程数,具体源码可以查看
      ThreadPoolExecutor.addWorker
    ThreadPoolExecutor.addWorker()
    
    else if (!addWorker(command, false))
    
    • 如果添加非核心线程任务失败了,我们就知道我们已经关闭或者饱和了所以拒绝这个任务。可以理解为拒绝策略
    else if (!addWorker(command, false))
                reject(command);
    

    6.ThreadFactory threadFactory (创建线程的工厂模式)

    使用默认即可
    Executors.defaultThreadFactory()
    

    7.RejectedExecutionHandler handler(拒绝策略):执行被阻止时要使用的处理程序,因为达到线程边界和队列容量

    8.拒绝策略的4种模式

    • AbortPolicy:默认策略,在拒绝任务时,会抛出RejectedExecutionException。
    • CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前的被丢弃的任务
    • DiscardOldestPolicy:该策略将丢弃最老的一个请求,也就是即将被执行的任务,并尝试再次提交当前任务。
    • 该策略默默的丢弃无法处理的任务,不予任何处理。

    9.图解线程池工作模式


    线程池工作模式

    10.常见线程池


    常见线程池

    11 常用的workQueue任务队列

    • SynchronousQueue:线程等待队列。同步队列,按序排队,先来先服务,并且不保留任务。为了避免当线程数达到maximumPoolSize造成的错误,所以maximumPoolSize通常设置无限大Integer.MAX_VALUE。

    • LinkedBlockingQueue:队列接受任务的时候,判断当前线程数是否小于核心线程数,如果小于,则新建核心线程处理任务,如果等于核心线程数,则进入队列等待。由于队列没有最大值的限制,所以超过核心线程数的任务,都会添加到队列任务中。所以,这种模式下maximumPoolSize的设置无效,因为总线程数=核心+非核心。

    • ArrayBlockingQueue:可以设置队列长度,跟LinkedBlockingQueue类似,最大区别,当前队列添加满的时候,会新建非核心线程执行任务,当核心+非核心> maximumPoolSize(线程总数),并且队列也满了,则抛出异常。

    • DelayQueue:队列入队,达到延迟时间后,执行任务,主要用来执行延迟任务操作

    线程跟线程池的关系

    • 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

    合理配置线程池

    1.cpu密集型:线程池中线程个数尽量少,常规配置(CPU核心数+1)
    2.io密集型:有io操作速度比cpu速度慢,所以运行这种类型的任务时,cpu处于空闲状态,那么线程池可以多分配线程数量,以此提高cpu利用率,常规配置(2*CPU核心数+1)
    3.乱七八糟任务:视情况进行拆解

    Okhttp线程池的使用

    • Dispatcher
    @get:JvmName("executorService") val executorService: ExecutorService
        get() {
          if (executorServiceOrNull == null) {
            executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
                SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
          }
          return executorServiceOrNull!!
        }
    

    根据源码可知,Okhttp使用SynchronousQueue,对标上文,当前线程池没有核心线程,同步队列,先来先执行,线程空闲后,不会保留,并且适用于短时间的任务操作。

    • SynchronousQueue每个插入操作必须等待另一个线程的移除操作,同样任何一个移除操作都等待另一个线程的插入操作。因此队列内部其实没有任何一个元素,或者说容量为0,严格说并不是一种容器,由于队列没有容量,因此不能调用peek等操作,因此只有移除元素才有新增的元素,显然这是一种快速传递元素的方式,也就是说在这种情况下元素总是以最快的方式从插入者(生产者)传递给移除者(消费者),这在多任务队列中最快的处理任务方式。对于高频请求场景,无疑是最合适的。

    • 在OKHttp中,创建了一个阀值是Integer.MAX_VALUE的线程池,它不保留任何最小线程,随时创建更多的线程数,而且如果线程空闲后,只能多活60秒。所以也就说如果收到20个并发请求,线程池会创建20个线程,当完成后的60秒后会自动关闭所有20个线程。他这样设计成不设上限的线程,以保证I/O任务中高阻塞低占用的过程,不会长时间卡在阻塞上。

    相关文章

      网友评论

          本文标题:Okhttp原理之线程池

          本文链接:https://www.haomeiwen.com/subject/ktyosltx.html