前言
Android开发中由于禁止在主线程中做网络请求,通常都需要使用线程对象来做异步请求操作,但是直接使用new Thread();创建新线程需要不停的申请系统资源,这些野生的线程缺乏统一管理,相互竞争占用过多系统资源;直接使用普通的线程做定期执行和线程中断等功能特别容易出错,为此 J.U.C类库里添加了功能强大的Executor框架,它能够为用户程序提供强大的线程池实现。使用JDK自带的线程池功能有如下的优点:
- 可以重用已有的线程,减少对象创建和销毁;
- 可以被有效控制最大并发线程数,提高系统资源利用率,避免过多资源竞争;
- 提供定时执行定期执行单线程和并发数据控制等功能
基础介绍
J.U.C的Excutor框架最主要的子类就是ThreadPoolExecutor,它是大部分线程池的底层实现对象,通常只需要学会如何配置它的运行参数就能够产生功能相当强大的线程池实现。它的构造函数主要包含如下几个参数:
参数名 | 作用 |
---|---|
corePoolSize | 核心线程数量 |
maximumPoolSize | 线程最大线程数 |
workQueue | 阻塞队列,存储等待执行的任务会对线程池线程运行过程产生重大影响 |
keepAliveTime | 线程没有任务执行是最多保持多久时间终止 |
unit | 时间单位 |
threadFactory | 线程工厂,用来创建线程 |
rejectedExcutionHandler | 如果提交的任务太多无法被处理,需要执行的拒绝策略 |
线程池合理配置,需要尽量压榨CPU,CPU密集型任务核心线程数参考值可以设置为NCPU+1,IO密集型任务参考值可以设置为2*NCPU
线程池内部使用状态机管理状态,主要分成运行、关闭、停止、清理、终止五种状态,它们之间的相互转换如下:
在关闭状态之前提交的任务会被继续执行完成,但新添加的任务会被抛弃,关闭状态会直接停止所有线程和正在执行的任务,清理完了任务和工作线程之后进入清理状态,最后完成终止回调进入终止状态,线程池就不能再使用了。
普通的Thread请求无法返回结果,而很多网络请求就是为了从服务器获取数据,直接使用Runnable就需要使用共享变量来接收返回结果,这种跨线程的数据请求很容易出现问题。Executor框架为用户提供了FutureTask类,它实现了RunnableFuture接口,而RunnableFuture实现了Runnable和Future两个接口,Future代表一个执行结果对象,调用它的get/cancel/isDown方法能够获取、取消和查看执行结果,传递进线程池的任务通常都会被封装成FutureTask对象来执行。
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
上面的代码就是AbstractExecutorService几个需要返回结果的执行接口,它们都会把Runnable或者Callable对象封装到RunnableFuture对象中去,execute接口不会做任何封装,它只接受Runnable类型的任务对象。
正常执行测试
前面已经了解过线程池对象的几个参数,现在使用自定义生成的参数来配置一个线程池,测试它在不同情况下的执行效果。首先为线程池添加自定义的ThreadFactory对象,这个线程工厂每次产生新线程的时候都会打印创建线程的名字。接着定义当任务过多线程池无法接受过多任务会执行拒绝策略,这里只是简单的打印出被拒绝的任务。
ThreadFactory threadFactory = new ThreadFactory() {
private AtomicLong mAtomicLong = new AtomicLong(0);
@Override
public Thread newThread(@NotNull Runnable r) {
Thread thread = new Thread(r, "My-Thread-" + mAtomicLong.getAndIncrement());
// 创建新线程的时候打印新线程名字
System.out.println("Create new Thread(): " + thread.getName());
return thread;
}
};
RejectedExecutionHandler handler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 打印被拒绝的任务
System.out.println("rejectedExecution" + r.toString());
}
};
接着来为线程池配置任务队列,也就是在核心线程已经全部创建而且都在执行任务,这时又有新的任务被提交它们就会被放置在任务队列中,任务队列可以分为有界队列和无界队列。
// 有界队列,最多能存放5个任务对象
BlockingQueue<Runnable> limitArray = new ArrayBlockingQueue<>(5);
BlockingQueue<Runnable> limitlist = new LinkedBlockingQueue<>(5);
// 无界队列可以存放Integer.MAX_VALUE个任务
BlockingQueue<Runnable> unlimitList &#