> 文档中心 > 浅谈鸿蒙线程管理

浅谈鸿蒙线程管理


概述

在启动应用时,系统会为该应用创建一个称为“主线程”的执行线程。该线程随着应用创建或消失,是应用的核心线程。在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 就是延迟时间,当延迟时间到达后该任务会被加入任务队列,但是实际执行时间可能要比这个时间完,取决于内部线程工作状态。

  • 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设置是没有意义,但是可以使用来分离不同的任务组,达到微观并行、宏观串行的行为。
上诉代码中的第一个,第二个任务执行顺寻不固定的,但是第三个任务一定是在前两个任务执行完后才会执行。

  • 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一一对应,只是在线程管理上有相似点,文中拿两者比较是为了便于理解。

说明

  • 本文中HarmonyOS相关素材来自于HarmonyOS Developer 开发文档。
  • 本文中Android相关素材来自于《Android开发艺术探索》。
  • 若有侵权或错误,请发送邮件至alphabetadata@163.com。