序 、最近几天心情不是太美丽 。
回顾
如果有人说在面试生涯中没有被面试过线程相关的技术点 ,我是不信的 。比如最基础的线程状态 、创建线程的几种方式 ;或者在问一下线程开发遇到的问题 ;还有一个知识点就是线程池了 ,优化多线程 。
先回顾一下 ,线程池是 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)调用流程梳理