早前的舊文中,我分享了使用 java.util.concurrent.Phaser
在處理大量異步任務場景下的使用。其中用到了phaser類的重要特性 可以靈活設置同步數量,在使用過程中註冊新的同步對象。
但是在後續的使用過程中遇到的一些問題,主要有一下兩點:
- 註冊同步等待總量有上限
private static final int MAX_PARTIES = 0xffff;
- 功能複雜,API豐富但大部分用不到,在使用過程中經常調錯API
今天終於無法忍受,特別是低一點,導致大量異步任務會丟數據。之前是按照非同步方式執行大量任務,但是今天遇到了不定任務量,一時沒想到這茬,導致了半個小時數據構造化爲泡影。
重寫思路
一怒之下,決定自己重寫一個加強版。總結了設計思路如下:
- 線程安全計數,用於統計完成任務數
- 線程安全狀態技術,用於統計多少任務尚未完成
- 註冊方法,用於增加任務統計技術
- 完成方法,用於減少未完成數量,增加完成任務數量
- 返回各類狀態信息的方法
實現這樣的功能,我們就得到了一個簡單但加強功能的多線程同步類,用來替代 java.util.concurrent.Phaser
,我命名爲 FunPhaser
。代碼如下:
package com.funtester.frame
import java.util.concurrent.atomic.AtomicInteger
/**
* 自定義同步類,避免{@link java.util.concurrent.Phaser}的不足,總數量受限於65535
* 用於多線程任務同步,任務完成後,調用{@link #done()}方法,任務總數減少,當任務總數爲0時,調用{@link #await()}方法,等待所有任務完成
*/
class FunPhaser extends SourceCode {
/**
* 任務總數索引,用於標記任務完成狀態
* 註冊增加,任務完成減少
*/
AtomicInteger index
/**
* 任務總數,用於記錄任務完成數量
*/
AtomicInteger taskNum
FunPhaser() {
this.index = new AtomicInteger()
this.taskNum = new AtomicInteger()
}
/**
* 註冊任務
* @return
*/
def register() {
this.index.getAndIncrement()
}
/**
* 任務完成
* @return
*/
def done() {
this.index.getAndDecrement()
this.taskNum.getAndIncrement()
}
/**
* 等待所有任務完成
* @return
*/
def await() {
waitFor {index.get() == 0}
}
/**
* 獲取任務完成總數
* @return
*/
int queryTaskNum() {
return taskNum.get()
}
}
源碼解讀
這個自定義同步類 FunPhaser
用於多線程任務同步,它避免了 java.util.concurrent.Phaser
的不足,即總數量受限於 65535。
FunPhaser
類有以下幾個成員變量:
index
:任務總數索引,用於標記任務完成狀態。註冊增加,任務完成減少。taskNum
:任務總數,用於記錄任務完成數量。
FunPhaser
類提供了以下幾個方法:
index
和taskNum
是AtomicInteger
類型的屬性,用於原子地操作整數值。FunPhaser()
是該類的構造函數,初始化了index
和taskNum
屬性。register()
方法用於註冊任務,每次調用會增加index
的值,表示新增一個任務。done()
方法用於標記任務完成,每次調用會減少index
的值並增加taskNum
的值。await()
方法用於等待所有任務完成。它調用了waitFor
方法,等待index
的值變爲 0,表示所有任務已經完成。queryTaskNum()
方法用於獲取任務完成的總數,返回taskNum
的值。
演示Demo
FunPhaser
類的使用方法如下:
import com.funtester.frame.FunPhaser;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FunPhaserDemo {
public static void main(String[] args) throws InterruptedException {
// 創建FunPhaser實例
FunPhaser phaser = new FunPhaser();
// 創建固定大小的線程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 註冊10個任務
for (int i = 0; i < 10; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
// 註冊任務
phaser.register();
// 模擬耗時操作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 標記任務完成
phaser.done();
}
});
}
// 等待所有任務完成
phaser.await();
// 輸出已完成的任務數量
System.out.println("已完成的任務數量: " + phaser.queryTaskNum());
// 關閉線程池
executorService.shutdown();
}
}
在這個示例中,我們創建了一個FunPhaser
對象,並使用固定大小爲5的線程池來執行10個異步任務。每個任務在開始前調用register()
方法註冊,完成後調用done()
方法標記。主線程通過調用await()
方法等待所有任務完成,並最終輸出已完成的任務數量。
自定義關鍵字
在自定義關鍵字中的使用如下:
/**
* 使用自定義同步器{@link FunPhaser}進行多線程同步
*
* @param f 代碼塊
* @param phaser 同步器
*/
public static void fun(Closure f, FunPhaser phaser) {
if (phaser != null) phaser.register();
ThreadPoolUtil.executeSync(() -> {
try {
f.call();
} finally {
if (phaser != null) {
phaser.done();
logger.info("async task {}", phaser.queryTaskNum());
}
}
});
}
作爲對照舊的實現代碼如下:
/**
* 異步執行代碼塊,使用{@link Phaser}進行多線程同步
*
* @param f 代碼塊
* @param phaser 同步器
*/
public static void fun(Closure f, Phaser phaser) {
if (phaser != null) phaser.register();
ThreadPoolUtil.executeSync(() -> {
try {
f.call();
} finally {
if (phaser != null) {
phaser.arrive();
logger.info("異步任務完成 {}", phaser.getArrivedParties());
}
}
});
}
這兩個實現代碼的功能都是相同的,都是使用同步器來進行多線程任務同步。
舊的實現代碼使用的是 Phaser
類。Phaser
類是一個通用的同步器,可以用於各種多線程任務同步場景。在舊的實現代碼中,我們使用 register()
方法來註冊任務,使用 arrive()
方法來表示任務完成。
新的實現代碼使用的是 FunPhaser
類。FunPhaser
類是一個自定義的同步器,它避免了 Phaser
類的不足,即總數量受限於 65535。在新的實現代碼中,我們使用 register()
方法來註冊任務,使用 done()
方法來表示任務完成。
兩種實現代碼的對比
指標 | 舊的實現代碼 | 新的實現代碼 |
---|---|---|
使用到的同步器 | Phaser |
FunPhaser |
總數量是否受限 | 是 | 否 |
代碼簡潔程度 | 較好 | 更好 |
總體而言,新的實現代碼比舊的實現代碼更加簡潔易用,並且避免了 Phaser
類的不足。