阿里巴巴Android开发手册对线程池使用的建议:
【推荐】ThreadPoolExecutor 设置线程存活时间(setKeepAliveTime),确保空闲时线程能被释放。
【强制】线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:
Executors 返回的线程池对象的弊端如下:
FixedThreadPool 和SingleThreadPool :允许的请求队列长度为
Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
CachedThreadPool 和ScheduledThreadPool :允许的创建线程数量为
Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
正例:
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES,
NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
taskQueue, new BackgroundThreadFactory(), new DefaultRejectedExecutionHandler());
反例:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
大部分情况我们使用Executors工具类去构建线程池,大概分为以下5种不同的线程池,如下
//构建线程数为1的线程池,等价于 newFixedThreadPool(1) 所构造出的线程池
Executors.newSingleThreadExecutor();
//构建包含固定线程数的线程池,默认情况下,空闲线程不会被回收
Executors.newFixedThreadPool(1);
//构建线程数不定的线程池,线程数量随任务量变动,空闲线程存活时间超过60秒后会被回收
Executors.newCachedThreadPool();
//构建核心线程数为 corePoolSize,可执行定时任务的线程池
Executors.newScheduledThreadPool(1);
//等价于 newScheduledThreadPool(1)
Executors.newSingleThreadScheduledExecutor();
ThreadManager 工具类,创建ThreadToolExecutor需要7个参数,如下
线程池-核心参数.png/**
* <p>
* 1.降低资源消耗
* 可以重复利用已创建的线程降低线程创建和销毁造成的消耗。
* 2.提高响应速度
* 当任务到达时,任务可以不需要等到线程创建就能立即执行。
* 3.提高线程的可管理性
* 线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
* <p>
* 最顶层的接口Executor仅声明了一个方法execute。
* ExecutorService 接口在其父接口基础上,声明了包含但不限于shutdown、shutdownNow、submit、invokeAll等方法。
* ScheduledExecutorService接口,
* 则是声明了一些和定时任务相关的方法:schedule、scheduleAtFixedRate等。
* 线程池的核心实现是在ThreadPoolExecutor类中,
* 我们使用Executors调用newFixedThreadPool、newSingleThreadExecutor和newCachedThreadPool
* 等方法创建线程池均是ThreadPoolExecutor类型。
*/
public class ThreadManager {
private static final String TAG = "ThreadManager";
/**
* 根据cup核心数设置线程池数量
* <p>
* 《Android开发艺术探索》一书中建议:
* 核心线程数等于CPU核心数+1;
*/
private static final int corePoolSize = Runtime.getRuntime().availableProcessors();
/**
* 最大线程池数量= cpu核心数*2+1
* <p>
* 《Android开发艺术探索》一书中建议:
* 线程池的最大线程数等于CPU的核心数的2倍+1;
*/
private static final int maximumPoolSize = corePoolSize * 2 + 1;
/**
* 等待线程的存活时间
* 核心线程无超时机制,分核心线程的闲置时间为4秒;
*/
private static final long keepAliveTime = 30;
/**
* 等待线程存活时间的单位
* <p>
* TimeUnit.MINUTES 代表六十秒的时间单位
*/
private static final TimeUnit unit = TimeUnit.MINUTES;
/**
* 3、 线程资源回收策略
* 考虑到系统资源是有限的,对于线程池超出 corePoolSize 数量的空闲线程应进行回收操作。进行此操作存在一个问题,即回收时机。
* 目前的实现方式是当线程空闲时间超过 keepAliveTime 后,进行回收。除了核心线程数之外的线程可以进行回收,核心线程内的空闲线程也可以进行回收。
* 回收的前提是allowCoreThreadTimeOut属性被设置为 true,通过public void allowCoreThreadTimeOut(boolean) 方法可以设置属性值。
* <p>
* 4、 排队策略
* 如上面线程创建规则所说的,当线程数量大于等于corePoolSize,workQueue未满时,则缓存新任务。
* 这里要考虑使用什么类型的容器缓存新任务,通过 JDK 文档介绍,
* 我们可知道有3种类型的容器可供使用,分别是同步队列,有界队列和无界队列。对于有优先级的任务,这里还可以增加优先级队列。
* 以上所介绍的4种类型的队列,对应的实现类如下:
* <p>
* SynchronousQueue 同步队列 该队列不存储元素,每个插入操作必须等待另一个线程调用移除操作,否则插入操作会一直阻塞
* ArrayBlockingQueue 有界队列 基于数组的阻塞队列,按照 FIFO 原则对元素进行排序
* LinkedBlockingQueue 无界队列 基于链表的阻塞队列,按照 FIFO 原则对元素进行排序
* PriorityBlockingQueue 优先级队列 具有优先级的阻塞队列
*/
private static final BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
/**
* 5、线程工厂。可通过工厂为新建的线程设置更有意义的名字
*/
private static final ThreadFactory threadFactory = Executors.defaultThreadFactory();
/**
* 6、拒绝策略
* 当线程池和任务队列均处于饱和状态时,使用拒绝策略处理新任务。默认是 AbortPolicy,即直接抛出异常
* <p>
* 如上线程创建规则策略中所说,当线程数量大于等于 maximumPoolSize,且 workQueue 已满,
* 或者是当前线程池被关闭了则使用拒绝策略处理新任务。Java 线程池提供了4种拒绝策略实现类,
* 如下:
* <p></p>
* AbortPolicy 丢弃新任务,并抛出 RejectedExecutionException
* DiscardPolicy 不做任何操作,直接丢弃新任务
* DiscardOldestPolicy 丢弃队列列首的元素,并执行新任务
* CallerRunsPolicy 会在线程池当前正在运行的Thread线程池中处理被拒绝的任务
*/
private static final RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy();
private static final ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
private ThreadManager() {
}
/**
* 饿汉单例缓存线程池
* 优点:对象优先创建,无须等待,效率高。
* 缺点:申明静态对象的时候就已经初始化,一定程度上造成了资源的浪费。
*/
private static final ThreadManager INSTANCE = new ThreadManager();
public static ThreadManager get() {
return INSTANCE;
}
/**
* submit在执行过程中与execute不一样,不会抛出异常而是把异常保存在成员变量中,
* 在FutureTask.get阻塞获取的时候再把异常抛出来
*/
public void execute(Runnable runnable) {
executor.execute(runnable);
}
/**
* 通过Future可以很轻易地获得任务的执行情况,比如是否执行完成、是否被取消、是否异常等等
*/
public Future<?> submit(Runnable runnable) {
return executor.submit(runnable);
}
/**
* 调用 shutdown 和 shutdownNow 方法关闭线程池后,就不能再向线程池提交新任务了。
* 对于处于关闭状态的线程池,会使用拒绝策略处理新提交的任务。
* <p>
* shutdown 会将线程池的状态设置为SHUTDOWN,同时该方法还会中断空闲线程
*/
public void shutdown() {
executor.shutdown();
}
/**
* shutdownNow 则会将线程池状态设置为STOP,并尝试中断所有的线程
*/
public void shutdownNow() {
executor.shutdownNow();
}
}
/**
* 饿汉单例缓存线程池
* 优点:对象优先创建,无须等待,效率高。
* 缺点:申明静态对象的时候就已经初始化,一定程度上造成了资源的浪费。
*/
public class ThreadUtils {
private static final ThreadUtils INSTANCE = new ThreadUtils();
private final ExecutorService threadPool = Executors.newCachedThreadPool();
private ThreadUtils() {
//构建线程数为1的线程池,等价于 newFixedThreadPool(1) 所构造出的线程池
Executors.newSingleThreadExecutor();
//构建包含固定线程数的线程池,默认情况下,空闲线程不会被回收
Executors.newFixedThreadPool(1);
//构建线程数不定的线程池,线程数量随任务量变动,空闲线程存活时间超过60秒后会被回收
Executors.newCachedThreadPool();
//构建核心线程数为 corePoolSize,可执行定时任务的线程池
Executors.newScheduledThreadPool(1);
//等价于 newScheduledThreadPool(1)
Executors.newSingleThreadScheduledExecutor();
}
public static ThreadUtils get() {
return INSTANCE;
}
public Future<?> submit(Runnable runnable) {
if (runnable != null) {
return threadPool.submit(runnable);
}
return null;
}
}
网友评论