概述
在启动应用时,系统会为该应用创建一个称为“主线程”的执行线程。该线程随着应用创建或消失,是应用的核心线程。在Java中默认一个进程只有一个主线程。因为主线程在任何时候都有较高的响应速度,所以UI界面的显示和更新等操作,都是在主线程上进行。主线程又称UI线程,默认情况下,所有的操作都是在主线程上执行。如果需要执行比较耗时的任务(如请求网络、下载文件、查询数据库),可创建其他线程(或子线程)来处理,否则主线程会因为耗时操作被阻塞从而出现ANR异常。 如果应用的业务逻辑比较复杂,可能需要创建多个线程来执行多个任务。这种情况下,代码复杂难以维护,任务与线程的交互也会更加繁杂。要解决此问题,HarmonyOS使用TaskDispatcher来分发不同的任务,Android中使用ThreadPoolExecutor(线程池)来管理线程,虽然叫法不同,但是HarmonyOS的任务分发器和Android的线程池有相似之处。
作用
TaskDispatcher虽然在定位上和Android的ThreadPoolExecutor差不多,但是两者的作用还是有很多不同之处:
| 作用 |
TaskDispatcher(HarmonyOS) | 分发不同的任务,管理多个线程 |
ThreadPoolExecutor(Android) | 重用线程,减少开销;控制并发,避免阻塞;简单管理线程,执行指定任务 |
从上表中可以看出:TaskDispatcher侧重于管理,而ThreadPoolExecutor侧重于节能减排。两者定位相似,但侧重点不同。 TaskDispatcher和ThreadPoolExecutor执行任务的方式也有很大不同;
- TaskDispatcher
public interface TaskDispatcher {
void syncDispatch(Runnable var1);
Revocable asyncDispatch(Runnable var1);
Revocable delayDispatch(Runnable var1, long var2);
void syncDispatchBarrier(Runnable var1);
void asyncDispatchBarrier(Runnable var1);
Group createDispatchGroup();
Revocable asyncGroupDispatch(Group var1, Runnable var2);
boolean groupDispatchWait(Group var1, long var2);
void groupDispatchNotify(Group var1, Runnable var2);
void applyDispatch(Consumer<Long> var1, long var2);
}
复制代码
上述代码中相关方法及参数说明如下:
方法 | 作用 |
syncDispatch | 同步派发任务 |
asyncDispatch | 异步派发任务 |
delayDispatch | 异步延迟派发任务 |
Group | 任务组 |
Revocable | 取消任务 |
syncDispatchBarrier | 同步设置屏障任务 |
asyncDispatchBarrier | 异步设置屏障任务 |
applyDispatch | 执行多次任务 |
具体使用在文中后半段进行介绍。
- ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler){...}
复制代码
参数 | 描述 |
corePoolSize | 线程池核心线程数 (默认一直活着) |
maximumPoolSize | 线程池最大线程数 |
keepAliveTime | 非核心线程超时时长即该线程闲置后能活多久 |
TimeUnit | 超时时长时间单位 |
workQueue | 线程池的任务队列 |
threadFactory | 线程工厂,为线程池提供新的线程 |
handler | 拒绝策略 |
TaskDispatcher对任务的执行方式做了进一步的封装,简单省事。ThreadPoolExecutor则对线程池的使用配备了多种属性,灵活多变。
分类
为了应对不同的任务需求,两种线程管理都对各自的线程进行了仔细的分类,大致都可以分为四种:
| 分类 |
TaskDispatcher(HarmonyOS) | GlobalTaskDispatcher、ParallelTaskDispatcher、SerialTaskDispatcher 、SpecTaskDispatcher |
ThreadPoolExecutor(Android) | FixedThreadPool、SingleThreadExecutor、CachedThreadPool、ScheduledThreadPool |
下文针对上表中的每种分类做简单的介绍和使用。
串行
- GlobalTaskDispatcher(HarmonyOS) 全局并发任务分发器,由Ability执行getGlobalTaskDispatcher()获取。适用于任务之间没有联系的情况。一个应用只有一个GlobalTaskDispatcher,它在程序结束时才被销毁。
TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
复制代码
TaskPriority.DEFAULT则是该任务的优先级。TaskDispatcher对任务优先级分:HIGH,DEFAULT,LOW。在UI线程上运行的任务默认以高优先级运行,如果某个任务无需等待结果,则可以用低优先级。
- SingleThreadExecutor(Android)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
复制代码
代码中参数说明该线程池有且只有一个核心线程,也说明该线程池是串行执行任务。所以SingleThreadExecutor通常用于统一所有任务到一个线程中,从而避免线程同步问题。 两者的相同之处在于只有一个线程,只能串行执行任务。不同在于GlobalTaskDispatcher一个应用只有一个,且任务之间没有联系,而SingleThreadExecutor没有此要求。
不固定线程
- SerialTaskDispatcher(HarmonyOS)
String dispatcherName = "serialTaskDispatcher";
TaskDispatcher serialTaskDispatcher = createSerialTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
复制代码
串行任务分发器,由Ability执行createSerialTaskDispatcher()创建并返回。由该分发器分发的所有的任务都是按顺序执行,但是执行这些任务的线程并不是固定的。它的创建和销毁由开发者自己管理,开发者在使用期间需要持有该对象引用。
- CachedThreadPool(Android)
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
复制代码
CachedThreadPool参数说明该线程池没有核心线程只有非核心线程,非核心线程数不固定,最大可以达到Integer.MAX_VALUE,基本上就是可以任意大。如果该线程池中线程都处于活动状态,该线程池就会创建新的线程来执行任务,否则就会利用空闲线程处理新任务,所以CachedThreadPool就相当于一个空集合,任何任务都会被立即执行,不会存在插入任务的情况。当线程处于空闲状态超过60s就会被回收,当所有线程都超时的时候,整个线程池就处于空闲状态,也就是没有任何线程,也就不会有任何资源的占用,所有通常被用来执行大量且耗时少的任务。
并发
- ParallelTaskDispatcher(HarmonyOS)
String dispatcherName = "parallelTaskDispatcher";
TaskDispatcher parallelTaskDispatcher = createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
复制代码
并发任务分发器,由Ability执行createParallelTaskDispatcher()创建并返回。与GlobalTaskDispatcher不同的是,ParallelTaskDispatcher不具有全局唯一性,可以创建多个。开发者在创建或销毁dispatcher时,需要持有对应的对象引用 。
- FixedThreadPool(Android)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
复制代码
FixedThreadPool是一种线程数量固定且只有核心线程的线程池。当线程处于空闲状态,该线程也不会被回收;当所有线程都处于活动状态,新的任务就会处于等待状态,直到有线程空闲出来。因为核心线程不会被回收,所以该线程池能够快速的响应外界请求,可用于页面交互。
专用
- SpecTaskDispatche(HarmonyOS)
TaskDispatcher uiTaskDispatcher = getUITaskDispatcher();
复制代码
专有任务分发器,绑定到专有线程上的任务分发器,主线程是专有线程。UITaskDispatcher和MainTaskDispatcher都属于SpecTaskDispatcher。建议使用UITaskDispatcher。 UITaskDispatcher:绑定到应用主线程的专有任务分发器, 由Ability执行getUITaskDispatcher()创建并返回。 由该分发器分发的所有的任务都是在主线程上按顺序执行,它在应用程序结束时被销毁。 MainTaskDispatcher:由Ability执行getMainTaskDispatcher()创建并返回。
TaskDispatcher mainTaskDispatcher= getMainTaskDispatcher()
复制代码
- ScheduledThreadPool(Android)
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
...
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
复制代码
ScheduledThreadPool核心线程是固定的,而非核心线程是没有限制的,当非核心线程处于空闲状态会立即被回收,通常用来执行具有定时或固定周期属性的任务。
使用
TaskDispatcher
- syncDispatch
globalTaskDispatcher.syncDispatch(new Runnable() {
@Override
public void run() {
...
}
});
复制代码
syncDispatch是同步派发任务并在当前线程等待任务执行完成,在返回之前,当前线程处于阻塞状态。如果同时创建多个任务,可以验证syncDispatch中任务是按顺序执行的。如果多个线程或任务分发器同时执行相同的任务,这时候使用syncDispatch会导致死锁,此时建议使用asyncDispatch任务。
- asyncDispatch
Revocable revocable = globalTaskDispatcher.asyncDispatch(new Runnable() {
@Override
public void run() {
...
}
});
复制代码
asyncDispatch派发任务是异步的,派发完任务就会立即返回,同时会返回一个可以取消的接口Revocable。使用asyncDispatch执行任务会发现执行任务的顺序是不固定的,同一任务可能先执行也可能后执行,取决于任务执行速度。
- delayDispatch
final long delayTime = 50;
Revocable revocable = globalTaskDispatcher.delayDispatch(new Runnable() {
@Override
public void run() {
...
}
}, delayTime );
复制代码
delayDispatch属于异步延迟派发任务,派发完后立即返回,内部会在指定的延迟时间后执行任务。delayTime 就是延迟时间,当延迟时间到达后该任务会被加入任务队列,但是实际执行时间可能要比这个时间完,取决于内部线程工作状态。https://www.xiaohongshu.com/discovery/item/5f92594700000000010097d9
- Group
TaskDispatcher dispatcher = context.createParallelTaskDispatcher(dispatcherName, TaskPriority.DEFAULT);
Group group = dispatcher.createDispatchGroup();
dispatcher.asyncGroupDispatch(group, new Runnable(){
public void run() {
...
}
});
dispatcher.asyncGroupDispatch(group, new Runnable(){
public void run() {
...
}
});
...
复制代码
当有多个相互联系的任务可以使用任务组Group,由TaskDispatcher执行createDispatchGroup创建并返回。该任务组创建后返回的也是可以取消的接口,同样也是异步的,任务执行的顺序不固定,取决于当前线程池状态。
- Revocable
TaskDispatcher dispatcher = context.getUITaskDispatcher();
Revocable revocable = dispatcher.delayDispatch(new Runnable(){
...
}, 10);
boolean revoked = revocable.revoke();
复制代码
Revocable是取消一个异步任务的接口。异步任务包括通过 asyncDispatch、delayDispatch、asyncGroupDispatch 派发的任务。如果任务已经在执行中或执行完成,则会返回取消失败,所以返回值可能是true,可能是false。
- syncDispatchBarrier
dispatcher.asyncGroupDispatch(group, new Runnable(){
public void run() {
...
}
});
dispatcher.asyncGroupDispatch(group, new Runnable(){
public void run() {
...
}
});
dispatcher.syncDispatchBarrier(new Runnable() {
public void run() {
...
}});
复制代码
syncDispatchBarrier就是在任务组上设立任务执行屏障,同步等待任务组中的所有任务执行完成,再执行指定任务。所以上诉代码中第一个任务,第二个任务执行顺序不固定,但是第三个任务一定会等前两个任务执行完后才会执行。在GlobalTaskDispatcher设置同步任务执行屏障是无效的,没有意义。
- asyncDispatchBarrier
dispatcher.asyncGroupDispatch(group, new Runnable(){
public void run() {
...
}
});
dispatcher.asyncGroupDispatch(group, new Runnable(){
public void run() {
...
}
});
dispatcher.asyncDispatchBarrier(new Runnable() {
public void run() {
...
}
});
复制代码
asyncDispatchBarrier在任务组上设立任务执行屏障后直接返回,指定任务将在任务组中的所有任务执行完成后再执行。同样在GlobalTaskDispatcher设置是没有意义,但是可以使用来分离不同的任务组,达到微观并行、宏观串行的行为。 上诉代码中的第一个,第二个任务执行顺寻不固定的,但是第三个任务一定是在前两个任务执行完后才会执行。https://www.xiaohongshu.com/discovery/item/5f929ddd000000000101f97a
- applyDispatch
final int total = 10;
dispatcher.applyDispatch((index) -> {
indexList.add(index);
latch.countDown();
}, total);
复制代码
applyDispatch是对指定任务执行多次。参数total就是需要执行任务的次数。
- ThreadPoolExecutor
Runnable command = new Runnable() {
@Override
public void run() {
Log.d("线程池","执行任务")
}
};
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
fixedThreadPool.execute(command);
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(command);
ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(command);
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(command,1000, TimeUnit.MILLISECONDS);
scheduledThreadPool.scheduleWithFixedDelay(command,2000,3000,TimeUnit.MILLISECONDS);
复制代码
上述代码中介绍了Android四种线程池的使用。Executors.newFixedThreadPool(2)是指定线程数为2,scheduledThreadPool.schedule(command,1000, TimeUnit.MILLISECONDS)是延迟1000毫秒执行任务,scheduledThreadPool.scheduleWithFixedDelay(command,2000,3000,TimeUnit.MILLISECONDS)是延迟2000毫秒后每个3000毫秒执行任务。具体使用方法不止上面几种,详细使用请自行查阅。
总结
- HarmonyOS对线程管理的更加细化,常用的几种情况都封装成立具体的方法,是先指定用途再指定方法,开发者使用的时候目的性会更加明确,操作稍显简单。Android对线程的管理更加灵活,线程池的使用方法在创建的时候就已指定,将用途和方法合并一步,而且通过参数进行配置,使用更加灵活,适用的场景也更多。总之,HarmonyOS灵活性体现在方法数量上(每一种用途都有相对应的方法),Android灵活性体现在构造函数的多样化(不同用途调动同样的方法只是参数不同)。
- HarmonyOS线程管理中对同步和异步都单独提出说明,Android没有做明显的区分,线程池的设计混合了这两种情况。
- HarmonyOS线程管理的线程执行次数、特定任务的执行顺序比较具有优势,Android实现这些效果需要额外控制。
- Android对线程的管理有管理也有节能减排低耗,而HarmonyOS侧重于管理。
- HarmonyOS的TaskDispatcher并非和Android的ThreadPoolExecutor一一对应,只是在线程管理上有相似点,文中拿两者比较是为了便于理解。