[英]Singleton service in Java and multithreading
我正在使用JAX-RS API開發Java服務。 我已經決定采用單例模式,但是現在我對如何管理並發性有所懷疑。
以下是我的代碼的簡化示例:
@Singleton
@Path("/")
public class NfvDeployer {
private static Map<String, List<String>> allocatedNodesOnHost;
private static Map<String, String> loadedHosts;
private static Map<String, String> loadedNodes;
...
@POST
@Path("nffgs/{id}/nodes")
@Produces(MediaType.APPLICATION_XML)
@Consumes(MediaType.APPLICATION_XML)
public MyNode postNodeOnNFFG(MyNode Node, @PathParam("id") String id) {
...
synchronized (this) {
...
allocatedNodesOnHost.get(H).add(Node.ID);
}
}
@GET
@Path("hosts/{id}/nodes")
@Produces(MediaType.APPLICATION_XML)
public MyNodes getNodeFromNFFG(@PathParam("id") String id) {
...
for(String S : allocatedNodesOnHost.get(id)) {
...
}
}
}
您認為這種方法可行嗎? 所有GET請求應同時發生,而POST請求應被序列化。 這是正確的嗎?
有些事情可能無法按預期工作。 首先,因為你可以聲明你NfvDeployer
為@Singleton
,你不必嚴格,使所有領域的靜態。 由於該框架將確保其中只有一個存在,因此應將其涵蓋在內。
關於同步,按原樣存在問題。 請考慮以下情況:寫入列表中包含為allocatedNodesOnHost
的值的列表,並同時遍歷同一列表。 您最終將得到ConcurrentModificationException
。 從Javadoc :
例如,通常不允許一個線程修改Collection而另一個線程對其進行迭代。
至少有兩種解決方案,它們取決於您想要哪種擔保。
選項1:一致的數據視圖
假設列表的讀者和列表的作者必須具有一致的列表視圖。 含義:如果有人寫一個新值是不正常的讀者查看列表中包含的值的稍舊的版本。 為了解決這個問題,我們應該同步對地圖的所有訪問(及其列表值):
public MyNode postNodeOnNFFG(MyNode node, @PathParam("id") String id) {
synchronized (allocatedNodesOnHost) {
allocatedNodesOnHost.get(id).add(node.ID);
}
}
public MyNodes getNodeFromNFFG(@PathParam("id") String id) {
synchronized (allocatedNodesOnHost) {
for(String S : allocatedNodesOnHost.get(id)) {
...
}
}
}
您會在這里注意到,我是在allocatedNodesOnHost
而不是this
上同步。 通過this
同步,我們獲得了非常粗糙的鎖,它會影響我們可能設置的任何其他同步塊。 同樣,為了避免ConcurrentModificationExceptions,對allocatedNodesOnHost
NodesOnHost的所有訪問都必須同步。
選項2:數據視圖不一致
當我說“不一致”時,我並不是說錯誤,而是意味着它可能已經過時,僅在並發修改的情況下。 本質上,我們將在選項1中執行的同步卸載到JVM提供的將為我們完成的結構上。
首先,我們將使用ConcurrentHashMap
聲明字段:
private Map<String, List<String>> allocatedNodesOnHost = new ConcurrentHashMap<>();
ConcurrentHashMap
為我們提供了一個地圖,使我們可以在不進行同步的情況下讀取和寫入地圖。 它避免了在可能的地方阻塞,因此,當您知道將有並發的讀取器和寫入器時,最好在多線程應用程序中使用它。
當我們在該映射中創建一個值時,將使用一個CopyOnWriteArrayList
,它被描述為:
ArrayList的線程安全變體,其中所有可變操作(添加,設置等)都通過對基礎數組進行全新復制來實現。 通常這樣做的成本太高,但是在遍歷操作的數量遠遠超過變異的情況下,它可能比替代方法更有效,並且在您無法或不想同步遍歷而又需要防止並發線程之間的干擾時很有用。
請注意警告:如果您無法/不會同步並且沒有頻繁的作者,請使用此選項。 如果您很少寫入列表,這是一個可行的選擇。
因此,當我們向allocatedNodesOnHost
映射添加新列表時,我們allocatedNodesOnHost
像這樣進行操作:
allocatedNodesOnHost.put(hostName, new CopyOnWriteArrayList<>());
然后,當我們訪問allocatedNodesOnHost
時,可以刪除同步:
public MyNode postNodeOnNFFG(MyNode node, @PathParam("id") String id) {
allocatedNodesOnHost.get(id).add(node.ID);
}
public MyNodes getNodeFromNFFG(@PathParam("id") String id) {
for(String S : allocatedNodesOnHost.get(id)) {
...
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.