Android 线程池封装库
0.前言介绍
- 轻量级线程池封装库,支持线程执行过程中状态回调监测(包含成功,失败,异常等多种状态);支持创建异步任务,并且可以设置线程的名称,延迟执行时间,线程优先级,回调callback等;可以根据自己需要创建自己需要的线程池,一共有四种;线程异常时,可以打印异常日志,避免崩溃。
- 关于线程池,对于开发来说是十分重要,但是又有点难以理解或者运用。关于写线程池的博客网上已经有很多了,但是一般很少有看到的实际案例或者封装的库,许多博客也仅仅是介绍了线程池的概念,方法,或者部分源码分析,那么为了方便管理线程任务操作,所以才想结合实际案例是不是更容易理解线程池,更多可以参考代码。
1.遇到的问题和需求
1.1 遇到的问题有哪些?
- 继承Thread,或者实现接口Runnable来开启一个子线程,无法准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果
- 当线程出现异常的时候,如何避免导致崩溃问题?
1.2 遇到的需求
- 如何在实际开发中配置线程的优先级
- 开启一个线程,是否可以监听Runnable接口中run方法操作的过程,比如监听线程的状态开始,成功,异常,完成等多种状态。
- 开启一个线程,是否可以监听Callable
接口中call()方法操作的过程,比如监听线程的状态开始,错误异常,完成等多种状态。
1.3 多线程通过实现Runnable弊端
1.3.1 一般开启线程的操作如下所示
1
2
3
4
5
6new Thread(new Runnable() {
@Override
public void run() {
//做一些任务
}
}).start();创建了一个线程并执行,它在任务结束后GC会自动回收该线程。
在线程并发不多的程序中确实不错,而假如这个程序有很多地方需要开启大量线程来处理任务,那么如果还是用上述的方式去创建线程处理的话,那么将导致系统的性能表现的非常糟糕。
1.3.2 主要的弊端有这些,可能总结并不全面
大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM
大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿
线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失
1.4 为什么要用线程池
- 使用线程池管理线程优点
- ①降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
- ②提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;
- ③方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;
- ④更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。
1.5 线程池执行流程
- 大概的流程图如下
- 文字描述如下
- ①如果在线程池中的线程数量没有达到核心的线程数量,这时候就回启动一个核心线程来执行任务。
- ②如果线程池中的线程数量已经超过核心线程数,这时候任务就会被插入到任务队列中排队等待执行。
- ③由于任务队列已满,无法将任务插入到任务队列中。这个时候如果线程池中的线程数量没有达到线程池所设定的最大值,那么这时候就会立即启动一个非核心线程来执行任务。
- ④如果线程池中的数量达到了所规定的最大值,那么就会拒绝执行此任务,这时候就会调用RejectedExecutionHandler中的rejectedExecution方法来通知调用者。
2.封装库具有的功能
2.1 常用的功能
- 支持线程执行过程中状态回调监测(包含成功,失败,异常等多种状态)
- 支持线程异常检测,并且可以打印异常日志
- 支持设置线程属性,比如名称,延时时长,优先级,callback
- 支持异步开启线程任务,支持监听异步回调监听
- 方便集成,方便使用,可以灵活选择创建不同的线程池
3.封装库的具体使用
3.1 一键集成
- compile ‘cn.yc:YCThreadPoolLib:1.3.0’
3.2 在application中初始化库
代码如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64public class App extends Application{
private static App instance;
private PoolThread executor;
public static synchronized App getInstance() {
if (null == instance) {
instance = new App();
}
return instance;
}
public App(){}
@Override
public void onCreate() {
super.onCreate();
instance = this;
//初始化线程池管理器
initThreadPool();
}
/**
* 初始化线程池管理器
*/
private void initThreadPool() {
// 创建一个独立的实例进行使用
executor = PoolThread.ThreadBuilder
.createFixed(5)
.setPriority(Thread.MAX_PRIORITY)
.setCallback(new LogCallback())
.build();
}
/**
* 获取线程池管理器对象,统一的管理器维护所有的线程池
* @return executor对象
*/
public PoolThread getExecutor(){
return executor;
}
}
//自定义回调监听callback,可以全局设置,也可以单独设置。都行
public class LogCallback implements ThreadCallback {
private final String TAG = "LogCallback";
@Override
public void onError(String name, Throwable t) {
Log.e(TAG, "LogCallback"+"------onError"+"-----"+name+"----"+Thread.currentThread()+"----"+t.getMessage());
}
@Override
public void onCompleted(String name) {
Log.e(TAG, "LogCallback"+"------onCompleted"+"-----"+name+"----"+Thread.currentThread());
}
@Override
public void onStart(String name) {
Log.e(TAG, "LogCallback"+"------onStart"+"-----"+name+"----"+Thread.currentThread());
}
}
3.3 最简单的runnable线程调用方式
关于设置callback回调监听,我这里在app初始化的时候设置了全局的logCallBack,所以这里没有添加,对于每个单独的执行任务,可以添加独立callback。
1
2
3
4
5
6
7
8
9PoolThread executor = App.getInstance().getExecutor();
executor.setName("最简单的线程调用方式");
executor.setDeliver(new AndroidDeliver());
executor.execute(new Runnable() {
@Override
public void run() {
Log.e("MainActivity","最简单的线程调用方式");
}
});
3.4 最简单的异步回调
如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26PoolThread executor = App.getInstance().getExecutor();
executor.setName("异步回调");
executor.setDelay(2,TimeUnit.MILLISECONDS);
// 启动异步任务
executor.async(new Callable<Login>(){
@Override
public Login call() throws Exception {
// 做一些操作
return null;
}
}, new AsyncCallback<Login>() {
@Override
public void onSuccess(Login user) {
Log.e("AsyncCallback","成功");
}
@Override
public void onFailed(Throwable t) {
Log.e("AsyncCallback","失败");
}
@Override
public void onStart(String threadName) {
Log.e("AsyncCallback","开始");
}
});
4.线程池封装思路介绍
4.1 自定义Runnable和自定义Callable类
4.1.1 首先看看Runnable和Callable接口代码
1
2
3
4
5
6
7public interface Runnable {
public void run();
}
public interface Callable<V> {
V call() throws Exception;
}4.1.2 Runnable和Callable接口是干什么的
Runnable 从 JDK1.0 开始就有了,Callable 是在 JDK1.5 增加的。
Thread调用了Runnable接口中的方法用来在线程中执行任务。Runnable 和 Callable 都代表那些要在不同的线程中执行的任务。
Thread调用了Runnable接口中的方法用来在线程中执行任务。
4.1.3 Runnable和Callable接口有何区别
它们的主要区别是 Callable 的 call() 方法可以返回值和抛出异常,而 Runnable 的 run() 方法没有这些功能。Callable 可以返回装载有计算结果的 Future 对象。
比较两个接口,可以得出这样结论:
Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的
call() 方法可以抛出异常,run()方法不可以的
运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果;
4.1.4 自定义Runnable包装类,重点看run方法代码逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59public final class RunnableWrapper implements Runnable {
private String name;
private CallbackDelegate delegate;
private Runnable runnable;
private Callable callable;
public RunnableWrapper(ThreadConfigs configs) {
this.name = configs.name;
this.delegate = new CallbackDelegate(configs.callback, configs.deliver, configs.asyncCallback);
}
/**
* 启动异步任务,普通的
* @param runnable runnable
* @return 对象
*/
public RunnableWrapper setRunnable(Runnable runnable) {
this.runnable = runnable;
return this;
}
/**
* 异步任务,回调用于接收可调用任务的结果
* @param callable callable
* @return 对象
*/
public RunnableWrapper setCallable(Callable callable) {
this.callable = callable;
return this;
}
/**
* 自定义xxRunnable继承Runnable,实现run方法
*/
@Override
public void run() {
Thread current = Thread.currentThread();
ThreadToolUtils.resetThread(current, name, delegate);
//开始
delegate.onStart(name);
//注意需要判断runnable,callable非空
// avoid NullPointException
if (runnable != null) {
runnable.run();
} else if (callable != null) {
try {
Object result = callable.call();
//监听成功
delegate.onSuccess(result);
} catch (Exception e) {
//监听异常
delegate.onError(name, e);
}
}
//监听完成
delegate.onCompleted(name);
}
}4.1.5 自定义Callable
包装类,重点看call方法代码逻辑 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48public final class CallableWrapper<T> implements Callable<T> {
private String name;
private ThreadCallback callback;
private Callable<T> proxy;
/**
* 构造方法
* @param configs thread配置,主要参数有:线程name,延迟time,回调callback,异步callback
* @param proxy 线程优先级
*/
public CallableWrapper(ThreadConfigs configs, Callable<T> proxy) {
this.name = configs.name;
this.proxy = proxy;
this.callback = new CallbackDelegate(configs.callback, configs.deliver, configs.asyncCallback);
}
/**
* 自定义Callable继承Callable<T>类,Callable 是在 JDK1.5 增加的。
* Callable 的 call() 方法可以返回值和抛出异常
* @return 泛型
* @throws Exception 异常
*/
@Override
public T call() {
ThreadToolUtils.resetThread(Thread.currentThread(),name,callback);
if (callback != null) {
//开始
callback.onStart(name);
}
T t = null;
try {
t = proxy == null ? null : proxy.call();
} catch (Exception e) {
e.printStackTrace();
//异常错误
if(callback!=null){
callback.onError(name,e);
}
}finally {
//完成
if (callback != null) {
callback.onCompleted(name);
}
}
return t;
}
}
4. 添加回调接口AsyncCallback和ThreadCallback
注意,这个写的自定义callback,需要添加多种状态,你可以自定义其他状态。看完了这里再回过头看看RunnableWrapper中run方法和CallableWrapper中call方法的逻辑。
4.0 为什么要这样设计
它可以让程序员准确地知道线程什么时候执行完成并获得到线程执行完成后返回的结果。
AsyncCallback,在这个类中,可以看到三种状态[这个是在自定义Runnable中的run方法中实现],并且成功时可以携带结果,在异常时还可以打印异常日志。
ThreadCallback,在这个类中,可以看到三种状态[这个是在自定义Callable
中的call方法中实现],并且在异常时可以打印异常日志,在开始和完成时可以打印线程名称 4.1 AsyncCallback类代码如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23/**
* 异步callback回调接口
*/
public interface AsyncCallback<T> {
/**
* 成功时调用
* @param t 泛型
*/
void onSuccess(T t);
/**
* 异常时调用
* @param t 异常
*/
void onFailed(Throwable t);
/**
* 通知用户任务开始运行
* @param threadName 正在运行线程的名字
*/
void onStart(String threadName);
}4.2 ThreadCallback类代码如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* 一个回调接口,用于通知用户任务的状态回调委托类,线程的名字可以自定义
*/
public interface ThreadCallback {
/**
* 当线程发生错误时,将调用此方法。
* @param threadName 正在运行线程的名字
* @param t 异常
*/
void onError(String threadName, Throwable t);
/**
* 通知用户知道它已经完成
* @param threadName 正在运行线程的名字
*/
void onCompleted(String threadName);
/**
* 通知用户任务开始运行
* @param threadName 正在运行线程的名字
*/
void onStart(String threadName);
}
4.3 创建线程池配置文件
为什么要添加配置文件,配置文件的作用主要是存储当前任务的某些配置,比如线程的名称,回调callback等等这些参数。还可以用于参数的传递!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public final class ThreadConfigs {
/**
* 线程的名称
* 通过setName方法设置
*/
public String name;
/**
* 线程执行延迟的时间
* 通过setDelay方法设置
*/
public long delay;
/**
* 线程执行者
* JAVA或者ANDROID
*/
public Executor deliver;
/**
* 用户任务的状态回调callback
*/
public ThreadCallback callback;
/**
* 异步callback回调callback
*/
public AsyncCallback asyncCallback;
}
4.4 创建java平台和android平台消息器Executor
在android环境下,想一想callback回调类中的几个方法,比如回调失败,回调成功,或者回调完成,可能会做一些操作UI界面的操作逻辑,那么都知道子线程是不能更新UI的,所以必须放到主线程中操作。
但是在Java环境下,回调方法所运行的线程与任务执行线程其实可以保持一致。
因此,这里需要设置该消息器用来区别回调的逻辑。主要作用是指定回调任务需要运行在什么线程之上。
4.4.1 android环境下
4.4.2 java环境下
4.4.3 如何判断环境是java环境还是Android环境呢
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public final class ThreadToolUtils {
/**
* 标志:是否在android平台上
*/
public static boolean isAndroid;
/*
* 静态代码块
* 判断是否是android环境
* Class.forName(xxx.xx.xx) 返回的是一个类对象
* 首先要明白在java里面任何class都要装载在虚拟机上才能运行。
*/
static {
try {
Class.forName("android.os.Build");
isAndroid = true;
} catch (Exception e) {
isAndroid = false;
}
}
}
4.5 创建PoolThread继承Executor
这里只是展示部分代码,如果想看完整的代码,可以直接看案例。
4.5.1 继承Executor接口,并且实现execute方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48public final class PoolThread implements Executor{
/**
* 启动任务
* 这个是实现接口Executor中的execute方法
* 提交任务无返回值
* @param runnable task,注意添加非空注解
*/
@Override
public void execute (@NonNull Runnable runnable) {
//获取线程thread配置信息
ThreadConfigs configs = getLocalConfigs();
//设置runnable任务
runnable = new RunnableWrapper(configs).setRunnable(runnable);
//启动任务
DelayTaskDispatcher.get().postDelay(configs.delay, pool, runnable);
//重置线程Thread配置
resetLocalConfigs();
}
/**
* 当启动任务或者发射任务之后需要调用该方法
* 重置本地配置,置null
*/
private synchronized void resetLocalConfigs() {
local.set(null);
}
/**
* 注意需要用synchronized修饰,解决了多线程的安全问题
* 获取本地配置参数
* @return
*/
private synchronized ThreadConfigs getLocalConfigs() {
ThreadConfigs configs = local.get();
if (configs == null) {
configs = new ThreadConfigs();
configs.name = defName;
configs.callback = defCallback;
configs.deliver = defDeliver;
local.set(configs);
}
return configs;
}
}
4.6 使用builder模式获取线程池对象
4.6.1 看下builder模式下代码
也可以看Android源码设计模式这本书,很不错
直接列出代码,如下所示:
1 | public final class PoolThread implements Executor{ |
4.6.2 添加设置thread配置信息的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43/**
* 为当前的任务设置线程名。
* @param name 线程名字
* @return PoolThread
*/
public PoolThread setName(String name) {
getLocalConfigs().name = name;
return this;
}
/**
* 设置当前任务的线程回调,如果未设置,则应使用默认回调。
* @param callback 线程回调
* @return PoolThread
*/
public PoolThread setCallback (ThreadCallback callback) {
getLocalConfigs().callback = callback;
return this;
}
/**
* 设置当前任务的延迟时间.
* 只有当您的线程池创建时,它才会产生效果。
* @param time 时长
* @param unit time unit
* @return PoolThread
*/
public PoolThread setDelay (long time, TimeUnit unit) {
long delay = unit.toMillis(time);
getLocalConfigs().delay = Math.max(0, delay);
return this;
}
/**
* 设置当前任务的线程传递。如果未设置,则应使用默认传递。
* @param deliver thread deliver
* @return PoolThread
*/
public PoolThread setDeliver(Executor deliver){
getLocalConfigs().deliver = deliver;
return this;
}4.6.3 看下builder模式下创建对象的代码
通过调用ThreadBuilder类中的build()方法创建属于自己的线程池。
最后通过new PoolThread(type, size, priority, name, callback, deliver, pool)创建对象,并且作为返回值返回。
然后再来看看PoolThread方法,这部分看目录4.7部分介绍。
4.7 灵活创建线程池[重点]
4.7.1 创建线程池的五种方法
通过Executors的工厂方法获取这五种线程池
通过Executors的工厂方法来创建线程池极其简便,其实它的内部还是通过new ThreadPoolExecutor(…)的方式创建线程池的,具体可以看看源码,这里省略呢……
1
2
3
4
5ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();4.7.2 灵活创建不同类型线程池
设计的时候,希望能够选择性地创建自己想要的线程池,并且动态设置线程的数量,还可以设置线程优先级。
4.7.2.1 创建不同类型线程池代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24/**
* 创建线程池,目前支持以下四种
* @param type 类型
* @param size 数量size
* @param priority 优先级
* @return
*/
private ExecutorService createPool(int type, int size, int priority) {
switch (type) {
case Builder.TYPE_CACHE:
//它是一个数量无限多的线程池,都是非核心线程,适合执行大量耗时小的任务
return Executors.newCachedThreadPool(new DefaultFactory(priority));
case Builder.TYPE_FIXED:
//线程数量固定的线程池,全部为核心线程,响应较快,不用担心线程会被回收。
return Executors.newFixedThreadPool(size, new DefaultFactory(priority));
case Builder.TYPE_SCHEDULED:
//有数量固定的核心线程,且有数量无限多的非核心线程,适合用于执行定时任务和固定周期的重复任务
return Executors.newScheduledThreadPool(size, new DefaultFactory(priority));
case Builder.TYPE_SINGLE:
default:
//内部只有一个核心线程,所有任务进来都要排队按顺序执行
return Executors.newSingleThreadExecutor(new DefaultFactory(priority));
}
}4.7.2.1 了解一下ThreadFactory接口作用
关于ThreadFactory接口的源代码如下所示:
以看到ThreadFactory中,只有一个newThread方法,它负责接收一个Runnable对象,并将其封装到Thread对象中,进行执行。
通过有道词典对这个类的说明进行翻译是:根据需要创建新线程的对象。使用线程工厂可以消除对{@link Thread#Thread(Runnable)新线程}的硬连接,从而使应用程序能够使用特殊的线程子类、优先级等。
1
2
3
4
5
6
7
8
9
10
11
12public interface ThreadFactory {
/**
* Constructs a new {@code Thread}. Implementations may also initialize
* priority, name, daemon status, {@code ThreadGroup}, etc.
*
* @param r a runnable to be executed by new thread instance
* @return constructed thread, or {@code null} if the request to
* create a thread is rejected
*/
Thread newThread(Runnable r);
}4.7.2.3 创建默认MyThreadFactory继承ThreadFactory
创建一个默认的MyThreadFactory,并且这个类继承ThreadFactory,实现接口中的newThread方法。然后在newThread方法中创建线程,并且设置线程优先级。
创建一个优先级线程池非常有用,它可以在线程池中线程数量不足或系统资源紧张时,优先处理我们想要先处理的任务,而优先级低的则放到后面再处理,这极大改善了系统默认线程池以FIFO方式处理任务的不灵活。
代码如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class MyThreadFactory implements ThreadFactory {
private int priority;
public MyThreadFactory(int priority) {
this.priority = priority;
}
@Override
public Thread newThread(@NonNull Runnable runnable) {
Thread thread = new Thread(runnable);
thread.setPriority(priority);
return thread;
}
}
4.8 启动线程池中的任务
具体逻辑看DelayTaskExecutor中的postDelay方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
* 启动
* @param delay 延迟执行的时间,注意默认单位是TimeUnit.MILLISECONDS
* @param pool pool线程池
* @param task runnable
*/
void postDelay(long delay, final ExecutorService pool, final Runnable task) {
if (delay == 0) {
//如果时间是0,那么普通开启
pool.execute(task);
return;
}
//延时操作
dispatcher.schedule(new Runnable() {
@Override
public void run() {
//在将来的某个时间执行给定的命令。该命令可以在新线程、池线程或调用线程中执行
pool.execute(task);
}
}, delay, TimeUnit.MILLISECONDS);
}
4.9 如何关闭线程池操作
代码如下所示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/**
* 关闭线程池操作
*/
public void stop(){
try {
// shutdown只是起到通知的作用
// 只调用shutdown方法结束线程池是不够的
pool.shutdown();
// (所有的任务都结束的时候,返回TRUE)
if(!pool.awaitTermination(0, TimeUnit.MILLISECONDS)){
// 超时的时候向线程池中所有的线程发出中断(interrupted)。
pool.shutdownNow();
}
} catch (InterruptedException e) {
// awaitTermination方法被中断的时候也中止线程池中全部的线程的执行。
e.printStackTrace();
} finally {
pool.shutdownNow();
}
}