在效能測試的實踐當中,非同步任務是離不開的。Java 非同步程式設計提高了應用程式的效能和響應性,透過避免執行緒阻塞提高了資源利用率,並簡化了併發程式設計的複雜性。改善使用者體驗,避免死鎖和執行緒阻塞等問題。非同步程式設計利用 CompletableFuture、Future 等工具和 API 簡化了開發流程,提高了系統的穩定性和可靠性。
緣起
我也參照了 Go
語言的 go
關鍵字,自定義了 fun
關鍵字Java 自定義非同步功能實踐 。但是在使用過程中,遇到了一個略顯尷尬的問題,就是如果當一個非同步任務中,又增加一個非同步任務,且使用集合點設定。那麼就會阻塞執行緒池,導致大量任務阻塞的情況。
比如一個學校,200 個班級,每個班級有一個班主任,要給 30 個學生髮作業,之後再報告作業分發已完成。按照之前的思路,我會包裝兩個非同步且設定集合點的任務,虛擬碼如下:
static void main(String[] args) {
200.times {
fun {
sleep(1.0)// 模擬業務處理
pushHomework()// 佈置作業
}
}
}
/**
* 佈置作業
*/
static void pushHomework() {
FunPhaser phaser = new FunPhaser()// 建立同步屏障
30.times {
fun {
sleep(1.0)// 模擬業務處理
output("佈置作業")
} , phaser
}
phaser.await()// 等待所有作業佈置完成
}
最終的結果就是,等於最大執行緒數的任務會阻塞在 pushHomework()
方法中,而 pushHomework()
方法需要完成的非同步任務又全都等待線上程池的等待佇列中。
初解
一開始我的思路採取優先順序策略。如果區分任務的優先順序,讓有可能阻塞在等待佇列的高優任務優先執行即可。所以我先想使用 java.util.concurrent.PriorityBlockingQueue
當做 java.util.concurrent.BlockingQueue
的實現當做非同步執行緒池的等待佇列。
但也無法解決問題,因為依然存在阻塞的問題,只不過機率變小了而已。看來不得不使用單獨的非同步執行緒池來實現了。
關於執行緒池的選擇有兩種選擇:
- 選擇最大執行緒數較小的執行緒池,只是作為輔助功能,防止阻塞。在普通非同步任務執行時,優先執行高優任務,利用普通執行緒池優先執行高優任務。
- 選擇最小執行緒數較大的執行緒池,大機率是快取執行緒池。單獨用來執行高優任務。同時也可以利用普通的執行緒池執行高優任務。
關於我的選擇,也沒有選擇。根據實際的情況使用吧。高優任務的多少、需要限制的頻率等等因素。我自己的專案用的是第二種,原因是我用到高優任務的機會不多,我可以在指令碼中控制高優任務的數量。
方案
首先是執行緒池的實現程式碼:
priorityPool = createFixedPool(POOL_SIZE, "P")
建立時機放在了普通執行緒池中:
/**
* 獲取非同步任務連線池
* @return
*/
static ThreadPoolExecutor getFunPool() {
if (asyncPool == null) {
synchronized (ThreadPoolUtil.class) {
if (asyncPool == null) {
asyncPool = createPool(POOL_SIZE, POOL_SIZE, ALIVE_TIME, new LinkedBlockingDeque<Runnable>(Constant.MAX_WAIT_TASK), getFactory("F"))
daemon()
}
priorityPool = createFixedPool(POOL_SIZE, "P")
// priorityPool = createPool(1, POOL_MAX, ALIVE_TIME, new LinkedBlockingQueue<Runnable>(10), getFactory("P"), new ThreadPoolExecutor.DiscardOldestPolicy())
}
}
return asyncPool
}
下面是呼叫執行高優的非同步任務的方法:
/**
* 執行高優非同步任務
* @param runnable
*/
static void executeSyncPriority(Runnable runnable) {
if (priorityPool == null) getFunPool()
priorityPool.execute(runnable)
}
還有一個呼叫方法,用來普通執行緒池優先執行高優任務:
/**
* 執行高優任務
*/
static void executePriority() {
def locked = priorityLock.compareAndSet(false, true)//如果沒有鎖,則加鎖
if (locked) {//如果加鎖成功
while (priorityPool.getQueue().size() > 0) {
def poll = priorityPool.getQueue().poll()
def queue = (LinkedBlockingDeque<Runnable>) getFunPool().getQueue()
if (poll != null) {
queue.offerFirst(poll)
}
}
priorityLock.set(false)//解鎖
}
}
這裡用到了一個原子類,當做高優之行時候的鎖 private static AtomicBoolean priorityLock = new AtomicBoolean(false)
,避免在這塊浪費過多效能。這裡沒有 try-catch-finally
,此處沒有使用,確實發生異常機率較小。
我重新修改了任務佇列的實現,用的是 java.util.concurrent.LinkedBlockingDeque
,這樣我就可以將高優任務直接插入到佇列的最前頭,可以優先執行高優任務。
對於非同步關鍵字,我也進行了一些改動:
/**
* 使用自定義同步器{@link FunPhaser}進行多執行緒同步
*
* @param f
* @param phaser
* @param log
*/
public static void fun(Closure f, FunPhaser phaser, boolean log) {
if (phaser != null) phaser.register();
ThreadPoolUtil.executeSync(() -> {
try {
ThreadPoolUtil.executePriority();
f.call();
} finally {
if (phaser != null) {
phaser.done();
if (log) logger.info("async task {}", phaser.queryTaskNum());
}
}
});
}
執行高優任務的關鍵字,我也進行了同樣的封裝,只不過換了個關鍵字和執行緒池:
/**
* 提交高優任務
*
* @param f
* @param phaser
* @param log
*/
public static void funny(Closure f, FunPhaser phaser, boolean log) {
if (phaser != null) phaser.register();
ThreadPoolUtil.executeSyncPriority(() -> {
try {
f.call();
} finally {
if (phaser != null) {
phaser.done();
if (log) logger.info("priority async task {}", phaser.queryTaskNum());
}
}
});
}
驗證
我們修改一下開始的指令碼:
static void main(String[] args) {
setPoolMax(2)
6.times {
fun {
sleep(1.0)// 模擬業務處理
pushHomework()// 佈置作業
}
}
}
/**
* 佈置作業
*/
static void pushHomework() {
FunPhaser phaser = new FunPhaser()// 建立同步屏障
4.times {
fun {
sleep(1.0)// 模擬業務處理
output("佈置作業")
} , phaser
}
phaser.await()// 等待所有作業佈置完成
}
執行的話,執行緒池的 F
執行緒全都全都是 TIME_WAITING
狀態。當把 pushHomework()
方法改成高優關鍵字 funny
之後問題便可迎刃而解。
控制檯輸出如下:
22:47:17:160 P-1 佈置作業
22:47:17:160 P-1 佈置作業
22:47:17:160 P-1 priority async task 3
22:47:17:160 P-1 priority async task 4
22:47:18:178 F-2 佈置作業
22:47:18:179 F-2 priority async task 3
22:47:19:183 F-2 佈置作業
可以看出,已經開始有了 F
執行緒執行高優任務了。
- 2021 年原創合集
- 2022 年原創合集
- 2023 年原創合集
- 介面功能測試專題
- 效能測試專題
- Java、Groovy、Go、Python
- 單元&白盒&工具合集
- 測試方案&BUG&爬蟲&UI 自動化
- 測試理論雞湯
- 社群風采&影片合集