android-lite-go 源码设计分析

LiteGo:阿里工程师马天宇开源的迷你的Android异步并发类库。

LiteGo 特性

可定义核心并发线程数,即同一时间并发的请求数量。

可定义等待排队线程数,即超出核心并发数后可排队请求数量。

可定义等待队列进入执行状态的策略:先来先执行,后来先执行。

可定义等待队列满载后处理新请求的策略:

  • 抛弃队列中最新的任务
  • 抛弃队列中最旧的任务
  • 抛弃当前新任务
  • 直接执行(阻塞当前线程)
  • 抛出异常(中断当前线程)

如何合理的估计同时并发的CPU数量?

参考如何合理地估算线程池大小?

最大并发数与CPU核心关系

首先我们经常在电脑介绍中听到双核四线程,因为支持超线程技术的CPU,单个核心可以同时并发两个线程进行协同工作,相比单线程效率更高,但也达不到想象中性能翻倍的效果。而不支持超线程技术的CPU,一个核心就只能以一个线程进行运算,手机上没有超线程技术所以手机上并发的最大线程数就等于核心数。

如果是CPU密集型应用,则线程池大小设置为N+1
如果是IO密集型应用,则线程池大小设置为2N+1
如果一台服务器上只部署这一个应用并且只有这一个线程池,那么这种估算或许合理,具体还需自行测试验证。

这里我认同lite go的理念

  • 清闲时线程不要多持,最好不要超过CPU数量,根据具体应用类型和场- 景来决策。
  • 瞬间并发不要过多,最好保持在CPU数量左右,或者可以多几个问题并不大。
    同时并发的线程数量不要过多,最好保持在CPU核数左右,过多了CPU时间片过多的轮转分配造成吞吐量降低,过少了不能充分利用CPU,并发数可以适当比CPU核数多一点没问题。

主要的类

  1. SmartExecutor 类实现了Executor 接口:智能并发调度执行器

PriorityRunnable 抽象类 子类需要实现 Runnable 接口:顾名思义 优先级Runnable ;含有int类型成员变量priority。

  1. SchedulePolicy 枚举类型:线程池等待队列进入执行状态的调度策略,目前有两种策略,一个是先进先出(队列),另一种是先进后出(栈)
  2. OverloadPolicy 枚举类型:线程池等待队列满载后处理新请求的装载策略,目前有五种策略
  • 抛弃队列中最新的任务
  • 抛弃队列中最旧的任务
  • 抛弃当前新任务
  • 直接执行(阻塞当前线程)
  • 抛出异常(中断当前线程)

在上面所有的类当中SmartExecutor 是这个库的核心类,接下来主要分析这个类的实现。在SmartExecutor类当中有一个非常重要的属性ThreadPoolExecutor(线程池执行器)这里是代理设计模式的体现,先了解一下ThreadPoolExecutor的构造函数参数含义publicThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize:核心线程数
核心线程会一直存活,及时没有任务需要执行
当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
maximumPoolSize:最大线程数
当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
当线程数=maximumPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
keepAliveTime:线程空闲时间
当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
如果allowCoreThreadTimeout=true,则会直到线程数量=0

rejectedExecutionHandler:任务拒绝处理器
两种情况会拒绝处理任务:
当线程数已经达到maximumPoolSize,切队列已满,会拒绝新任务
当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
线程池会调用rejectedExecutionHandler来处理这个任务。如果没有设置默认是AbortPolicy,会抛出异常
ThreadPoolExecutor类有几个内部实现类来处理这类情况:
AbortPolicy 丢弃任务,抛运行时异常
CallerRunsPolicy 执行任务
DiscardPolicy 忽视,什么都不会发生
DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
实现RejectedExecutionHandler接口,可自定义处理器

ThreadPoolExecutor执行顺序

线程池按以下行为执行任务
  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数,且任务队列已满
  4. 若线程数小于最大线程数,创建线程
  5. 若线程数等于最大线程数,抛出异常,拒绝任务

android-lite-go
SmartExecutor 的几个主要方法:

public Future submit(Runnable task)

public Future submit(Runnable task, T result)

public Future submit(Callable task)

public void submit(RunnableFuture task)

public void execute(final Runnable command)
最主要的是 execute 方法,其他几个方法是将任务封装为 FutureTask 投入到 execute 方法执行。因为 FutureTask 本质就是一个 RunnableFuture 对象,兼具 Runnable 和 Future 的特性和功能。
execute 整个过程简单概括为:

把任务封装为一个类似“链表”的结构体,执行完一个,调度下一个。
加锁防止并发时抢夺资源,判断当前运行任务数量。
当前任务数少于并发最大数量则投入运行,若满载则投入等待队列尾部。
若等待队列未满新任务进入排队,若满则执行满载处理策略。
当一个任务执行完,其尾部通过“链接”的方式调度一个新任务执行。若没有任务,则结束。
其中「加锁」和将任务包装成「链表」是重点。

你可能感兴趣的:(android-lite-go 源码设计分析)