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