我正處于 Java 實驗系統的開發階段,該系統帶有一個沉重的 MySQL DB,包含數千條記錄,每條記錄都需要并行執行許多操作。
我很清楚使用 Java 執行緒,但我不知道在資料庫的大量記錄上使用它的最佳/有效方法是什么。
假設我們查看以下 DB 表:
Table technicians
ID NUMBER
DISTRICT_CODE NUMBER NOT NULL
EVENT_START_DATE DATE NOT NULL
EVENT_END_DATE DATE NOT NULL
INCHARGE NUMBER NOT NULL
EFFECTIVE_FROM DATE DEFAULT SYSDATE NOT NULL
EFFECTIVE_TO DATE
STATUS NUMBER NOT NULL
然后我們將執行以下提取:
SELECT * FROM technicians WHERE INCHARGE = 23;
現在,我正在認真考慮是否將提取的資訊放入串列(例如,ArrayList)或其他資料結構中,(注意每次提取包含大約 4000 條記錄,并且每 3 秒一次又一次地發生一次)以及如何分別為每個記錄實作執行緒。
提出的幼稚想法是,在查詢資料庫并接收資訊后,回圈遍歷每個記錄條目(sql.hasNext () 例如)并在每個記錄上運行 ThreadPoolExecutor 物件,但我傾向于相信有更有效和更快的方法。
歡迎任何建議
編輯:我看到有人對每條記錄要采取的行動提出了問題,所以我會嘗試回答這個問題。
對于每一行,我們將為每個欄位運行幾個不同的API,以確保其答案型別的正確性(例如正確、不正確、正確但值短等)等等。
對我來說重要的是要注意,每個操作都針對系統外部的 API(位于不同的遠程服務器上)發生,因此有時對于單個欄位,會多次呼叫不同的 API,因此功能強大并行作業很重要。
例如:
對于 INCHARGE 欄位 - 我們會將值發送到將檢查資料的外部 API 源,如果資訊正確,我們將再次將該欄位發送到另一個 API,我們將獲得與其相關的資訊。
uj5u.com熱心網友回復:
您似乎想每三秒處理一次資料庫中的一些行。每次,您要查詢大約四千行。這些行中的每一行都需要單獨處理,而不考慮該表中的其他行。聽起來您不是在更新行,而是通過呼叫其他服務(例如進行 Web 服務呼叫)來發送行的資料。
是的,使用執行器服務
因此,將您的資料加載到記憶體中,因為音量似乎很低。定義一個類來保存每一行的資料。由于我們主要使用這個類來透明和不可變地傳遞資料,因此將類定義為記錄。
record Technician ( int id , LocalDate eventStart , … ) {}
在回圈查詢結果集時實體化這些Technician物件。
對于每個Technician物件,傳遞給實作類的建構式Callable。run該類的方法定義了您在處理該行的資料、傳遞給 Web 服務呼叫等方面需要做的作業。
ACallable回傳一個值。讓我們定義另一個記錄來表示成功/失敗和記錄的 ID。
record TechnicianProcessingResult ( int id , boolean succeeded ) {}
使該記錄成為我們的型別Callable。
class ProcessTechnicianTask implements Callable< TechnicianProcessingResult > {
private final Technician technician ;
ProcessTechnicianTask( Technician t ) { // Constructor.
this.technician = t ;
}
public TechnicianProcessingResult call() {
System.out.println( "Processing technician Id " this.technician.id );
…
return new TechnicianProcessingResult( this.technician.id , true ) ;
… or …
return new TechnicianProcessingResult( this.technician.id , false ) ;
}
}
Technician為從資料庫檢索的每一行實體化的每個物件實體化一個任務。收集任務。
List< ProcessTechnicianTask > tasks = new ArrayList<>() ;
…
tasks.add( new ProcessTechnicianTask( nthTechnician ) ) ;
將該任務集合提交給您已經建立的執行器服務。通常指定幾乎與可用 CPU 內核一樣多的執行緒。
ExecutorService executorService = Executors.newFixedThreadPool( 5 ) ;
…
List< Future< TechnicianProcessingResult > > futures = executorService.invokeAll( tasks , 3 , TimeUnit.SECONDS ) ;
注意超時引數,以防出現問題并且您的任務需要太多時間才能完成。
檢查期貨串列以查看它們是否已完成,是否已取消,并檢查它們的結果物件。
你想每三秒重復一次。所以還要創建一個單執行緒ScheduledExecutorService. 安排一個重復的任務,a Runnableor Callable,它完成上述資料庫查詢作業,實體化Technician物件,將每個物件分配給一個ProcessTechnicianTask物件,所有這些都提交給我們的其他執行器服務。
一定要優雅地關閉你的執行器服務物件。否則它們的后備執行緒池可能會無限期地繼續運行,就像僵尸???♂?。請參閱 Javadoc 中提供的樣板代碼。
所有這些都已經在 Stack Overflow 上多次介紹過。因此,搜索以了解更多資訊。
您似乎已經想到了這種方法。但是您想知道是否有“更有效和更快的方法”。不,我沒有看到更好的方法。您的瓶頸是對您的 API 進行網路呼叫,大概是 Web 服務呼叫。與等待網路呼叫回應的執行緒相比,創建記錄物件、收集它們并將它們提交給執行器服務將非常快。
專案織機
在您的場景中可能會顯著提高性能的一件事是Project Loom承諾的虛擬執行緒和結構化并發。
在當前的 Java 中,每個 Java 執行緒都直接映射到主機作業系統執行緒。在 Web 服務呼叫期間,您的幾個執行緒中的每一個都將處于空閑狀態,暫停執行直到這些呼叫回傳。這些執行緒是重量級的,所以我們不能有很多。
在 Project Loom 中,許多虛擬執行緒映射到每個主機作業系統執行緒。這些虛擬執行緒是輕量級的,所以我們可以擁有數千甚至數百萬。當虛擬執行緒阻塞時,例如等待您的 Web 服務呼叫回傳,該虛擬執行緒將從主機作業系統執行緒“停放”/“卸載”,以便另一個虛擬執行緒可能使用主機執行緒。因此,其他虛擬執行緒可以在前一個虛擬執行緒等待其 Web 服務呼叫回傳時完成作業。
在您的情況下,一次可以進行更多的 Web 服務呼叫,而不是一次只呼叫幾個。您機器上的 CPU 內核會更加忙碌,從而在更短的時間內處理更多的行。您可能會看到處理時間增加了多倍。
Project Loom 尚未完成,但正在積極開發中。現在可以使用基于早期訪問 Java 19 的實驗性構建。請參閱 Ron Pressler 和 Alan Bateman 等團隊成員的文章、采訪和演示。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/475216.html
