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

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

 

回顾

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

先回顾一下 ,线程池是 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());

        for (int i = 0; i < 20; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                        Log.i("TTTTTTTTTTTTT", "当前线程名字是 :" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
        }

打印结果

OKHTTP 源码分析(2)线程池梳理_第1张图片

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());

        for (int i = 0; i < 20; i++) {
            executorService.execute(new Runnable() {
                @Override
                public 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());

        for (int i = 0; i < 20; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                        Log.i("TTTTTTTTTTTTT", "当前线程名字是 :" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });

打印结果

OKHTTP 源码分析(2)线程池梳理_第2张图片

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(), 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(), new ThreadFactory() {
            @Override
            public 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() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        Log.i("TTTTTTTTTTTTT", "并发 = 当前线程名字是 :" + Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            });
        }

打印结果

OKHTTP 源码分析(2)线程池梳理_第3张图片

 

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

往期回顾 

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

 

你可能感兴趣的:(源码分析)