当前位置: 代码迷 >> 综合 >> OKHTTP 源码分析(2)线程池梳理
  详细解决方案

OKHTTP 源码分析(2)线程池梳理

热度:104   发布时间:2023-11-17 10:33:36.0

序 、最近几天心情不是太美丽  。

 

回顾

如果有人说在面试生涯中没有被面试过线程相关的技术点 ,我是不信的 。比如最基础的线程状态 、创建线程的几种方式 ;或者在问一下线程开发遇到的问题 ;还有一个知识点就是线程池了 ,优化多线程 。

先回顾一下 ,线程池是 Java 1.5 之后才出现的饿 API 。

1. 最顶层的接口  Executor ,里面一个方法 execute ,传入一个 Runnable 参数 ;

2. 继承 Execute 的子类  ExecutorService 也属于顶层接口 ,里面多了一些操作方法 ;

3. 实现接口的 ExecutorService 的抽象类 AbstractExecutorService  ;

4. 继承抽象类 AbstractExecutorService 的 ThreadPoolExecutor ,我们平常使用的线程池也就是 ThreadPoolExecutor 。

 

常用的线程池

Java通过Executors提供四种线程池,分别为: 

1、newSingleThreadExecutor 

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

2、newFixedThreadPool 

创建一个指定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

3、newScheduledThreadPool 

创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。 

4、newCachedThreadPoo 

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 

实例代码

        //缓存线程池ExecutorService cachedThreadPool = Executors.newCachedThreadPool();//指定线程池ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);//单例线程池ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();//可调度线程池ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);

线程池参数

        /*** todo 参数一:corePoolSize 核心线程数, 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。* todo 参数二:maximumPoolSize, 线程池规定大小* todo 参数三四:时间单位*          正在执行的任务 Runnable20 > corePoolSize --- > 参数三四才会起作用;*          Runnable 执行完毕后 ,闲置 60 s ,如果过了闲置60s ,会回收掉 Runnable 任务 ;*          如果在闲置时间 60s ,复用此线程 Runnable* todo 参数五:workQueue 队列*           会把超出的线程加入队列中* todo 参数六:ThreadFactory threadFactory, // 新线程的产生方式**/

 

代码测试

1.  核心线程数为 1 ,线程池大小为 1  ,时间为 60s 的线程池 。

···         参数一:corePoolSize 核心线程数 -----1参数二:maximumPoolSize, 线程池规定大小 ------ 1参数三四:时间单位   -----60s正在执行的任务 Runnable数量20 > corePoolSize --- > 参数三四才会起作用;Runnable 执行完毕后 ,闲置 60 s ,如果过了闲置60s ,会回收掉 Runnable 任务 ; 如果在闲置时间 60s ,复用此线程 Runnable参数五:workQueue 队列 ,会把超出的线程加入队列中
···ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());for (int i = 0; i < 20; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(100);Log.i("TTTTTTTTTTTTT", "当前线程名字是 :" + Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}});}

打印结果

PS:正在执行的任务 Runnable 数量为 20  > corePoolSize (核心线程数),参数三参数四才会起作用 ,Runnable 执行完毕后 ,闲置 60 s ,如果过了闲置60s ,会回收掉 Runnable 任务 ;如果在闲置时间 60s 内,则复用此线程 Runnable 。可能有点懵 ,简单来说就是如果核心线程数会复用 ,如果把核心线程数调到 2的话 ,就会看到两个线程在工作 。

2. 核心线程数为 5 ,线程池大小为 2  ,时间为 60s 的线程池 。

    ExecutorService executorService = new ThreadPoolExecutor(5, 2, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());for (int i = 0; i < 20; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(100);Log.i("TTTTTTTTTTTTT", "当前线程名字是 :" + Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}});}

PS:直接崩溃 ,参数异常 。核心线程数大于线程池的最大容量 ,还搞个毛线 。

3.核心线程数为 0 ,线程池大小设置为最大  ,时间为 60s 的线程池 。  缓存线程池

 ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());for (int i = 0; i < 20; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(100);Log.i("TTTTTTTTTTTTT", "当前线程名字是 :" + Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}});

打印结果

PS:可以看到这个是线程池是一直在复用同一个线程 。核心线程数为 0 的情况下 ,不考虑并发 ,只会有一个线程再跑 ,然后一直循环复用 ,所以我们就看到只有一个线程在不停的复用 。

 

OKHTTP 线程池

可以回顾下 OKHTTP 源码分析(1) 

在 OKHTTP 的 任务调度器中 Dispatcher 中 enqueue 方法 。

任务调度器 :Dispatcher synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);线程池executorService().execute(call);} else {readyAsyncCalls.add(call);}}

我们进入看下 executorService 方法 。

典型的缓存线程池策略public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));}return executorService;}

PS:OKHTTP 里面使用的线程池是缓存线程池策略 。后面的线程工厂 。进入 Util.threadFactory 里面是如下代码 。

Util 类的静态方法 threadFactory 方法 。public static ThreadFactory threadFactory(final String name, final boolean daemon) {return new ThreadFactory() {@Override public Thread newThread(Runnable runnable) {Thread result = new Thread(runnable, name);result.setDaemon(daemon);return result;}};}

设置线程名字 和设置是否为守护线程 。

示例代码

      ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {@Overridepublic Thread newThread(Runnable r) {Thread thread = new Thread(r);thread.setDaemon(false);thread.setName("My - OkHttp Dispatcher");return thread;}});for (int i = 0; i < 20; i++) {executorService.execute(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);Log.i("TTTTTTTTTTTTT", "并发 = 当前线程名字是 :" + Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}});}

打印结果

 

PS:OKHTTP 使用的是缓存线城市策略 。

往期回顾 

OKHTTP源码分析(1)调用流程梳理

 

  相关解决方案