概述

在启动应用时,系统会为该应用创建一个称为“主线程”的执行线程。该线程随着应用创建或消失,是应用的核心线程。在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一一对应,只是在线程管理上有相似点,文中拿两者比较是为了便于理解。