1. 程式人生 > 實用技巧 >淺談鴻蒙執行緒管理

淺談鴻蒙執行緒管理

概述

在啟動應用時,系統會為該應用建立一個稱為“主執行緒”的執行執行緒。該執行緒隨著應用建立或消失,是應用的核心執行緒。在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一一對應,只是在執行緒管理上有相似點,文中拿兩者比較是為了便於理解。