程式碼審查清單:Java併發 - Roman Leventov

banq發表於2019-02-03

Apache Druid社群,我們目前正在準備一份詳細的清單,以便在程式碼審查期間使用。我決定將清單的一部分作為媒體上的帖子釋出,以收集更多關於清單專案的想法。希望有人會發現它在實踐中很有用。
順便說一句,似乎我為程式碼審查建立專案特定的清單應該是一個強大的想法,但我沒有在大型開源專案中看到任何現有的例子。有意思,為什麼?
這篇文章包含有關多執行緒Java程式碼出現的問題的清單專案。
感謝Marko TopolnikMatko MedenjakChris VestSimon WillnauerBen Manes對本文的評論和貢獻。檢查表不完整,歡迎提出意見和建議!

1.設計
1.1  如果補丁引入了一個帶有併發程式碼的新子系統,那麼在補丁描述中是否需要合併併發化?是否討論了可以簡化程式碼併發模型的替代設計方法(參見下一個專案)?


1.2  是否可以應用一個或多個設計模式(其中一些在下面列出)以顯著簡化程式碼的併發模型,同時不會明顯影響其他質量方面,如整體簡單性,效率,可測試性,可擴充套件性等?

不變性/快照服務。當某個狀態應該更新時,會建立,釋出和使用新的不可變物件(或可變物件中的快照),而某些併發執行緒仍可能使用較舊的副本或快照。參見CopyOnWriteArrayList,CopyOnWriteArraySet,持久資料結構

分而治之。工作分為幾個獨立處理的部分,每個部分都在一個執行緒中。然後結合處理結果。Parallel Streams或ForkJoinPool可用於應用此模式。

生產者-消費者。工作執行緒透過佇列在工作執行緒之間傳輸。參見[JCIP 5.3],本清單中的專案6.1,CSPSEDA

例項限制。某些根型別的物件封裝了一些複雜的分層子狀態。Root物件主要負責從多個執行緒訪問和修改子狀態的安全性。換句話說,組合物件是同步的,而不是組成同步物件。見[JCIP 4.2,10.1.3,10.1.4]。

執行緒/任務/序列執行緒限制。透過自上而下的傳遞引數或ThreadLocal使得某些狀態成為本地狀態。見[JCIP 3.3]。任務限制是執行緒限制思想的變體,與分而治之模式結合使用。它通常以lambda捕獲的“context”引數或每個執行緒任務物件中的欄位的形式出現。序列執行緒限制是生產者 - 消費者模式的執行緒限制思想的擴充套件,參見[JCIP 5.3.2]。

2. 文件
2.1 對於具有執行緒安全標誌的每個類,方法和欄位,例如synchronized關鍵字,volatile欄位上的修飾符,使用任何類java.util.concurrent.*或第三方併發原語或併發集合,需要實現他們的Javadoc註釋包括:

  • 執行緒安全的理由:是否解釋了為什麼特定的類,方法或欄位必須是執行緒安全的?
  • 併發控制流文件:它是從什麼方法和上下文中列舉執行緒安全類的每個特定方法被呼叫的執行緒(執行程式,執行緒池)的內容?

2.2 如果補丁引入了一個使用執行緒或執行緒池的新子系統,那麼是否有執行緒模型的高階描述,子系統的併發控制流(或資料流),例如在包中的Javadoc註釋package-info.java或對於子系統的主類?新增新執行緒或執行緒池或從系統中刪除一些舊執行緒時,這些描述是否保持最新?

執行緒模型的描述包括子系統中建立和管理的執行緒和執行緒池的列舉,子系統中使用的外部池(例如ForkJoinPool.commonPool()),它們的大小和其他重要特性(如執行緒優先順序)以及託管執行緒的生命週期和執行緒池。

併發控制流的高階描述應該是概述,並將各個類的併發控制流文件聯絡在一起,請參閱上一項。如果使用生產者 - 消費者模式,則併發控制流是微不足道的,應該記錄資料流。

描述執行緒模型和控制/資料流極大地提高了系統的可維護性,因為在沒有描述或圖表的情況下,開發人員花費大量時間和精力在他們的腦海中建立和重新整理這些模型。放下模型也有助於發現瓶頸和簡化設計的方法(參見第1.2項)。

2.3 對於作為公共API的一部分或專案的擴充套件API的類和方法:是否在它們的Javadoc註釋中指定它們是否是(或者在擴充套件中為子類化設計的介面和抽象類的情況下,它們是否應實現為)不可變,執行緒安全或不是執行緒安全的?對於(或應該實現為)執行緒安全的類和方法,是否可以精確地記錄可以從多個執行緒同時呼叫它們的其他方法(或它們自己)?另見[EJ Item 82]和[JCIP 4.5]。

2.4 對於使用某些併發設計模式的子系統,類,方法和欄位,可以是高階別(例如本清單中第1.2項中提到的那些)或低階別(例如雙重檢查鎖定,請參閱此核對表中的第8節) ):在各個子系統,類,方法和欄位的設計或實現註釋中是否發現了使用的併發模式?這有助於讀者更快地理解程式碼。

2.5 ConcurrentHashMap和ConcurrentSkipListMap物件儲存在ConcurrentHashMap或者ConcurrentSkipListMap或ConcurrentMap型別欄位和變數的中,不僅僅是Map?
這很重要,因為在如下程式碼中:

ConcurrentMap<String, Entity> entities = getEntities();
if (!entities.containsKey(key)) {
  entities.put(key, entity);
} else {
  ...
}

很明顯可能存在競爭條件,如果併發執行緒在containsKey()和put()之間同時呼叫,一個實體會被兩個併發執行緒放入Map中。
如果entities變數的型別只是Map<String, Entity>它不那麼明顯,讀者可能會認為這只是稍微不理想的程式碼並且透過。
可以將此建議轉換 IntelliJ IDEA中的檢查

banq注:由於過於冗長複雜,且可能過於教條,點選標題參考原文

相關文章