2008-10-16 70 views
7

我有一個List對象被多個線程訪問。主要有一個線程,並且在某些情況下有兩個線程更新列表。根據正在處理的用戶請求的數量,可以從此列表讀取一到五個線程。 該列表不是要執行的任務隊列,而是正在檢索並同時更新的域對象的列表。最好的辦法被訪問同時

現在有幾種方法可以使訪問該表的線程安全:
次使用synchronized塊
次使用正常(即讀,寫OPS共享同一個鎖)
次使用ReadWriteLock中
- 使用新ConcurrentBLABLBA集合類的一個

我的問題:
什麼是使用的最佳方法,因爲該cricital部分通常不包含很多操作(大多隻是添加/刪除/插入或正從列表元素)?
你能否推薦另一種方法,以上未列出?

一定限制
- 最優的性能是至關重要的,內存使用量沒有那麼多
- 它必須是一個有序列表(當前正在同步上的ArrayList),雖然不是一個排序列表(即不使用排序比較或比較器,但根據插入順序)
- 該列表將很大,最多包含100000個域對象,因此使用類似CopyOnWriteArrayList的東西不可行
- 寫入/更新ciritical部分通常非常快速,只需添加/刪除/插入或替換(設置​​)
-the讀操作會做主要是ElementAt的(指數)調用的大部分時間,但也有一些讀操作可能會做一個二進制搜索或者的indexOf(元素)
- 沒有直接迭代在列表完成,但同樣的操作,的indexOf(..)會遍歷列表

回答

3

你是否必須使用順序列表?如果地圖類型結構更合適,則可以使用ConcurrentHashMap。在列表中,ReadWriteLock可能是最有效的方法。

編輯以反映OP的編輯:在插入順序二進制搜索?你在二進制搜索中存儲時間戳並將其用於比較?如果是這樣,您可以使用時間戳作爲密鑰,並使用ConcurrentSkipListMap作爲容器(它維護密鑰順序)。

+0

我喜歡ConcurrentSkipListMap的想法。在90%的時間內,列表根據某個時間戳(每個域對象的ID的一部分)進行排序,所以它可能值得優化。仍然會考慮其他10%。 – 2008-10-17 08:10:23

1

什麼的線程讀取幹什麼?如果他們遍歷列表,那麼您確實需要確保在整個迭代過程中沒有人觸摸列表,否則可能會得到非常奇怪的結果。

如果你能準確地你所需要的語義定義,應該可以解決問題 - 但是你可能會發現,你需要編寫自己的集合類型正確,高效地做到這一點。另外,CopyOnWriteArrayList可能已經足夠好了 - 如果潛在的昂貴。基本上,你可以將你的要求越多,效率就越高。

+0

我看了一下CopyOnWriteArrayList,但是使用起來太昂貴了。該列表可能有100000個元素,並且會得到更新。 – 2008-10-16 09:15:10

+2

好的,在這種情況下,你需要仔細研究你的語義。這些更新是做插入/刪除,還是隻是替換元素?如果他們正在添加/刪除,他們是在列表的首尾處做的? (在這種情況下,鏈表可以真正幫助你。) – 2008-10-16 09:40:16

1

我不知道這是針對該問題的更多鈔票的解決方案,但......這對我來說很有意義使用到數據庫經理認爲,龐大的數據量,讓它管理事務

+0

數據在服務器端由DB管理器和Appserver層管理,但我們需要以某種方式將其顯示給最終用戶。這個列表的管理員發生在檢索數據的客戶端。 – 2008-10-16 10:27:05

1

我第二Telcontar's suggestion數據庫,因爲它們實際上是爲管理這個數據的規模和線程之間的協商而設計的,而內存中的集合則不是。

你說數據在服務器上的數據庫上,而客戶端上的本地列表是爲了用戶界面。您不需要一次將所有100000個項目保留在客戶端上,或對其執行如此複雜的編輯。在我看來,你想在客戶端上的是一個輕量級緩存到數據庫。

編寫一個只存儲客戶端當前數據子集的緩存。此客戶端緩存不會對其自己的數據執行復雜的多線程編輯;相反,它會將所有編輯提供給服務器,並偵聽更新。當服務器上的數據發生變化時,客戶端只會忘記舊數據並重新加載。只有一個指定的線程被允許讀取或寫入集合本身。通過這種方式,客戶端僅僅反映了服務器上發生的編輯,而不需要複雜的編輯。

是的,這是一個相當複雜的解決方案。它的成分是:

  • 一種用於裝載各種數據的協議,說項目478712至478901,而不是整個事情
  • 用於接收關於改變的數據
  • 緩存類更新協議通過服務器上的已知索引存儲項目
  • 屬於與服務器通信的高速緩存的線程。這是寫入集合本身的唯一線程
  • 屬於該緩存時數據被檢索
  • 該UI組件實現,讓他們收到數據時已經裝好了一個接口,它處理回調線程

起初刺,這個緩存的骨頭可能是這個樣子:

class ServerCacheViewThingy { 
    private static final int ACCEPTABLE_SIZE = 500; 
    private int viewStart, viewLength; 
    final Map<Integer, Record> items 
      = new HashMap<Integer, Record>(1000); 
    final ConcurrentLinkedQueue<Callback> callbackQueue 
      = new ConcurrentLinkedQueue<Callback>(); 

    public void getRecords (int start, int length, ViewReciever reciever) { 
     // remember the current view, to prevent records within 
     // this view from being accidentally pruned. 
     viewStart = start; 
     viewLenght = length; 

     // if the selected area is not already loaded, send a request 
     // to load that area 
     if (!rangeLoaded(start, length)) 
      addLoadRequest(start, length); 

     // add the reciever to the queue, so it will be processed 
     // when the data has arrived 
     if (reciever != null) 
      callbackQueue.add(new Callback(start, length, reciever)); 
    } 

    class Callback { 
     int start; 
     int length; 
     ViewReciever reciever; 
     ... 
    } 

    class EditorThread extends Thread { 

     private void prune() { 
      if (items.size() <= ACCEPTABLE_SIZE) 
       return; 
      for (Map.Entry<Integer, Record> entry : items.entrySet()) { 
       int position = entry.key(); 
       // if the position is outside the current view, 
       // remove that item from the cache 
       ... 
      } 
     } 

     private void markDirty (int from) { ... } 

     .... 
    } 

    class CallbackThread extends Thread { 
     public void notifyCallback (Callback callback); 
     private void processCallback (Callback) { 
      readRecords 
     } 
    } 
} 

interface ViewReciever { 
    void recieveData (int viewStart, Record[] records); 
    void recieveTimeout(); 
} 

有很多你必須填寫自己,很明顯的細節。

1

您可以使用實現同步的包裝:

import java.util.Collections; 
import java.util.ArrayList; 

ArrayList list = new ArrayList(); 
List syncList = Collections.synchronizedList(list); 

// make sure you only use syncList for your future calls... 

這是一個簡單的解決方案。在使用更復雜的解決方案之前,我會嘗試一下。