2012-01-24 30 views
3

我有我的(Java)代碼的關鍵部分,基本上像下面的代碼片段。他們從nio服務器進來。異步原子陣列

void messageReceived(User user, Message message) { 
    synchronized(entryLock) { 
     userRegistry.updateLastMessageReceived(user,time()); 
     server.receive(user,message); 
    } 
} 

但是,我的消息的很大比例不會改變服務器狀態,真的。他們只是客戶說「你好,我還在這裏」。我真的不希望在同步模塊內部做到這一點。

我可以使用同步映射或類似的東西,但它仍然會產生同步懲罰。

我真的很想做的是有一些像一個下拉框,這樣

void messageReceived(User user, Message message) { 
    dropbox.add(new UserReceived(user,time()); 
    if(message.getType() != message.TYPE_KEPT_ALIVE) { 
     synchronized(entryLock) { 
      server.receive(user,message); 
     } 
    } 
} 

我有一個清理程序來自動把那些不活躍睡覺客戶。因此,清理例程可以簡單地在單個同步塊中編譯保持活動的消息,而不是在每個保持活動的消息上同步以更新註冊表。因此,自然而然地,重新構建對此的需求,我做的第一件事就是開始制定解決方案。然後我決定這是一個非平凡的課程,而且這個問題很可能相當普遍。所以我在這裏。

tl; dr是否有Java庫或其他解決方案我可以用來促進以原子方式以異步方式添加到對象列表?不需要以異步方式從列表中收集數據。我只是不想在每次添加到列表上進行同步。

+1

您的收存箱是否需要跟蹤用戶發出的所有「嗨,我還在這裏」的消息,還是最後一個?如果更晚,您可以使用Map 來存儲它們,並覆蓋以前的任何地址。避免一些「這可能會增長很大」的問題。顯然你需要一個處理併發的Map。 – user949300

+0

@ user949300:我認爲保管箱OP的討論需要跟蹤所有消息,包括那些不是心跳的消息。現在,也許只有最後一次心跳纔可以,但Dropbox仍然需要包含所有非心跳信息。 *(至少這是以上代碼的樣子)* – TacticalCoder

+0

@ user949300它只需要最後一個。但是,我預計(可能錯誤地)找到一張原子圖是非常困難的。我的代碼中已經有了你所描述的地圖,但我不希望更新它到同步塊中(除非我在一個塊中完成所有的更新)。我認爲這也解決了988052的評論。 – corsiKa

回答

1

嗯,有幾件事情你必須記住。

首先,如果幾乎沒有爭用(同時嘗試進入同步塊的線程不止一個),那麼「同步成本」很少。其次,如果存在爭用,無論您使用何種技術,都會產生一些成本。保羅對ConcurrentLinkedQueue是正確的,「無等待」意味着線程併發控制不是使用鎖來完成的,但仍然會爲爭用付出一些代價。你可能也想看看ConcurrentHashMap,因爲我不確定你要找的清單是什麼。使用這兩個類是非常簡單和常見的。

如果您想要更冒險,可以在java.util.concurrent.atomic中找到一些非鎖定同步原語。

+0

我從一開始就考慮過一個CHashMap,但是它的鎖定映射正是我想要避免的。我認爲保羅的建議是要完成這項工作,但坦率地說,非鎖定地圖將是'完美'的解決方案。 – corsiKa

+0

哦,對於非鎖定地圖,請看看Cliff Click的[high-scale-lib](http://sourceforge.net/projects/high-scale-lib/)。它有一個名爲NonBlockingHashMap的非鎖定映射。但坦率地說,如果你認爲地圖或隊列能夠完成這項工作(你想要一個或另一個),並且更重要的是,由於你堅持這樣一個事實,我認爲你並不完全理解你的問題不使用鎖。無論是否使用鎖,任何同步都會帶來成本。有時鎖比非鎖定解決方案更快。 – pron

+0

如果你想只是自動更新計數器,並且事先知道客戶端的最大數量,則不會比[AtomicIntegerArray](http://docs.oracle.com/javase/1.5.0/)更快docs/api/java/util /併發/原子/ AtomicIntegerArray.html) – pron

0

有一件事我們可以做的是用於保活消息,一個簡單的ArrayList:

  1. 不斷加入到這個名單,只要每個保活消息出現。
  2. 另一個線程會在鎖X上同步並讀取並處理 保持活動狀態。請注意,此線程不會從列表中刪除只讀 閱讀/複製。
  3. 最後在messageReceived自己你檢查列表是否增長 說超過1000,在這種情況下,你同步鎖X和清除 列表。

    List keepAliveList = new ArrayList(); 空隙的messageReceived(用戶用戶,消息消息){ 如果(message.getType()== message.TYPE_KEPT_ALIVE){ 如果(keepAliveList.size()> THRESHOLD){ 同步(X){ processList.addAll(列表); list.clear(); } } keepAliveList.add(message); } }

在另一個線程上 空隙checkKeepAlives(){ 同步(X){ PROCESSLIST

//。addAll(list) } processKeepAlives(processList); }

+0

但是添加到ArrayList原子中?如果兩個線程嘗試在相同的位置添加它時間,是他們中的一個會迷路嗎? – corsiKa

+0

@ glowcoder你是對的,添加到列表並不是原子的,錯過了 –

2

ConcurrentLinkedQueue權利要求是:

這實現採用基於一個由馬吉德阿卜M在簡單,快速,和實際的非阻塞和阻塞併發隊列算法描述了有效的「無等待」算法Michael和Michael L. Scott。

我不確定什麼引號「等待免費」,但Concurrent *類是尋找結構,像你正在尋找的好地方。

您可能也有興趣以下內容:Effective Concurrency: Lock-Free Code — A False Sense of Security。它談論這些事情要做到多大,即使對專家來說也是如此。

+0

這正是我不想自己做的原因。併發性好,但我不是Joshua Bloch,如果有一位工程師一次只做這些東西,但我寧願使用他們的庫,如果可以的話,我會更願意使用他們的庫。當然。我還會看看你鏈接的博客文章。希望它裏面有庫,但如果它不存在,它至少應該有一些常見的陷阱。謝謝! – corsiKa

+0

這是一個很好的答案,只是不是這個問題。這真的不是glowcoder需要的。 – pron