目录
1. 前言
在Android开发中,当我们需要处理耗时任务时,我们都会新建一个子线程来处理这个耗时任务,处理完任务后,销毁释放该子线程。但是,当我们需要处理大量的异步耗时任务时,如果我们还是手动新建一个又一个子线程来处理任务时,即麻烦,又浪费CPU资源,因为线程的创建与销毁都是会消耗CPU资源,这时我们就需要用到线程池来帮我们管理子线程。
1.1 线程池的优点
引用自任玉刚老师的《Android开发艺术探索》
- 重用线程池中的线程,避免因为线程的创建与销毁所带来的性能开销。
- 能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。
- 能够对线程进行简单的管理,并提供定时执行以及指定时间间隔循环执行等功能。
2. 线程池的具体说明
下面来具体学习一个线程池的知识点。
2.1 Executor 是什么
package java.util.concurrent;
public interface Executor {
void execute(Runnable var1);
}
Executor是执行Runnable任务的接口,将任务的提交与任务的运行分离开。参考官方文档 Executor
public class ThreadPoolActivity extends AppCompatActivity {
private static final String TAG = "ThreadPoolActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread_pool);
MyExecutor myExecutor = new MyExecutor();
myExecutor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG, "MyExecutor run: " + Thread.currentThread().getId());
}
});
DirectExecutor directExecutor = new DirectExecutor();
directExecutor.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG, "DirectExecutor run: " + Thread.currentThread().getId());
}
});
}
/**
* 任务在除调用者线程之外的某个线程中执行。
*/
public class MyExecutor implements Executor {
@Override
public void execute(@NonNull Runnable runnable) {
//为每个任务生成一个新线程
new Thread(runnable).start();
}
}
/**
* Executor 立即在调用者的线程中运行提交的任务
*/
public class DirectExecutor implements Executor {
@Override
public void execute(@NonNull Runnable runnable) {
//executor立即在调用者的线程中运行提交的任务
runnable.run();
}
}
}
2.2 ExecutorService 是什么
public interface ExecutorService extends Executor {
void shutdown();
boolean isShutdown();
···
略
···
}
是一个继承Executor接口的接口,用于管理线程池中的线程。具体方法见下,参考官方文档 ExecutorService
例子:利用Executors创建一个定长线程池,利用ExecutorService来管理。
//利用Executors创建线程数量为2的定长线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
for (int i = 0; i < 10; i++) {
final int finalI = i;
//向线程池提交任务
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG, " Executors.newFixedThreadPool run: " + finalI);
}
});
try {
//让线程等待一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//当i==5时,关闭线程池
if (i == 5) {
fixedThreadPool.shutdown();
Log.d(TAG, "executorService shutdown.");
break;
}
}
效果为:
D/ThreadPoolActivity: Executors.newFixedThreadPool run: 0
D/ThreadPoolActivity: Executors.newFixedThreadPool run: 1
D/ThreadPoolActivity: Executors.newFixedThreadPool run: 2
D/ThreadPoolActivity: Executors.newFixedThreadPool run: 3
D/ThreadPoolActivity: Executors.newFixedThreadPool run: 4
D/ThreadPoolActivity: Executors.newFixedThreadPool run: 5
D/ThreadPoolActivity: executorService shutdown.
2.3 Executors 是什么
定义Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类的工厂和实用方法。该类支持以下几种方法。参考官方文档 Executors
public class Executors {
Executors() {
throw new RuntimeException("Stub!");
}
public static ExecutorService newFixedThreadPool(int nThreads) {
throw new RuntimeException("Stub!");
}
public static ExecutorService newSingleThreadExecutor() {
throw new RuntimeException("Stub!");
}
public static ExecutorService newCachedThreadPool() {
throw new RuntimeException("Stub!");
}
···
略
···
}
Executors包含的4种类型线程池:
/** 定长线程池
* 创建线程数量固定的线程池,线程会一直存在直到调用shutdown()才会被回收
* @param nThreads 定义线程池中的线程数
* @return (ExecutorService) 新创建的线程池
*/
public static ExecutorService newFixedThreadPool(int nThreads) {
throw new RuntimeException("Stub!");
}
/** 定时线程池
* 创建一个线程池,可以调度命令在给定的延迟后运行,或者定期执行
* @param corePoolSize 即使它们处于空闲状态,也要保留在池中的线程数
* @return (ScheduledExecutorService)返回一个新创建的定时线程池
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
throw new RuntimeException("Stub!");
}
/** 可缓存线程池
* 创建一个 根据需要创建新线程 的线程池,可重用以前构造的线程(如果没有可重用的线程则重新创建一个新的线程)。
* 如果一个线程超过60s没被使用,就会被终止及回收,不会浪费资源,所以适用于线程数量多,但线程耗时断短的情况
* @return (ExecutorService) 新创建的线程池
*/
public static ExecutorService newCachedThreadPool() {
throw new RuntimeException("Stub!");
}
/** 单线程化线程池
* 创建一个Executor,它使用一个在无界队列中运行的工作线程。
* 保证任务按顺序执行,并且在任何给定时间不会有多个任务处于活动状态。
* 保证不使用其他线程来执行程序,所以与newFixedThreadPool(1)是不同的。
* @return 新创建的单线程Executor
*/
public static ExecutorService newSingleThreadExecutor() {
throw new RuntimeException("Stub!");
}
使用方法:
- 创建线程池
- 向线程池中提交任务,ScheduledThreadPool用schedule()方法,其余三种线程池用execute()方法
- 关闭线程池
Runnable runnable = new Runnable() {
@Override
public void run() {
Log.d(TAG, "scheduledThreadPool run: ");
}
};
//1.创建定时线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
//2.调用schedule()方法,延时15s后向线程池中提交runnable任务
scheduledThreadPool.schedule(runnable, 15, TimeUnit.SECONDS);
//3.关闭线程池
scheduledThreadPool.shutdown();
/**==========================================================================*/
//1.创建线程池
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//2.调用execute()方法向线程池中提交任务
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG, "cachedThreadPool run: ");
}
});
//3.关闭线程池
cachedThreadPool.shutdown();
2.4 ThreadPoolExecutor 是什么
public class ThreadPoolExecutor extends AbstractExecutorService {
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
throw new RuntimeException("Stub!");
}
···
略
···
}
继承 AbstractExecutorService,对线程池实现精确控制。
构造函数参数分析:
- corePoolSize:线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即使核心线程没有任务在执行处于闲置状态,我们固定一定数量的核心线程且它一直存活这样就避免了一般情况下CPU创建和销毁线程带来的开销。如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设置为true,那么闲置的核心线程在等待新任务的到来时就会有超时策略,这个时间由 keepAliveTime 来设定,即当等待时间超过 keepAliveTime 所指定的时间后,核心线程就会被终止。allowCoreThreadTimeOut 默认为 false,即核心线程默认是没有超时时间的。
- maximumPoolSize:线程池中所能容纳的最大线程数。最大线程数 = 核心线程 + 非核心线程。非核心线程只有在核心线程用完且当前线程总数小于最大线程数时才会被创建,并且在执行完任务后非核心线程会被销毁。当活动线程数到达最大线程数后,后续的新任务线程就将被阻塞。
- keepAliveTime:非核心线程的存活时间,当线程数大于核心线程数时,非核心线程(多余的空闲线程)将等待新任务,如果它们在此参数定义的时间内没有获得任务,则它们将被终止回收。当 allowCoreThreadTimeOut 属性设置为 true 时,keepAliveTime 也作用在核心线程上。
- unit:keepAliveTime的时间单位,是一个枚举,如TimeUnit.MINUTES...
- workQueue:线程池中的任务队列,仅保存可运行的任务。它必须是 BlockingQueue。线程池中提交的 Runnable 对象。
例子:
private final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
private final int KEEP_ALIVE_TIME = 1;
private final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
private BlockingQueue<Runnable> taskQueue = new LinkedBlockingDeque<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES,
NUMBER_OF_CORES * 2,
KEEP_ALIVE_TIME,
KEEP_ALIVE_TIME_UNIT,
taskQueue);
executorService.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG, "executorService run: ");
}
});
executorService.shutdown();
ThreadPoolExecutor 执行任务的规则为:
- 当线程池中的核心线程数量未达到核心线程数时,启动一个核心线程去执行任务;
- 如果线程池中的核心线程数量达到最大核心线程数时,那么任务会被插入到任务队列中排队等待执行;
- 如果在上一步骤中任务队列已满但是线程池中线程数量未达到最大线程数,那么启动一个非核心线程来处理任务;
- 如果上一步骤中线程数量达到了最大线程数,那么线程池将拒绝执行该任务,且 ThreadPoolExecutor 会调用 RejectedtionHandler 的 rejectedExecution 方法来通知调用者。
3. 阿里指南
如果你的Android Studio安装了Alibaba Java Coding Guidelines,那么如果你按照上诉方法,用Executors来创建线程池,那你肯定会遇到一系列警告,如:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。创建线程池的时候请使用带ThreadFactory的构造函数,并且提供自定义ThreadFactory实现或者使用第三方实现。
那么,该如何创建线程池呢?
未完待续~