簡體   English   中英

在Java 6中使用的最佳方法,可以同時訪問List

[英]Best approach to use in Java 6 for a List being accessed concurrently

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

現在有幾種方法可以訪問此列表的線程安全:
- 使用同步塊
- 使用普通 (即讀寫操作共享相同的鎖)
- 使用ReadWriteLock
- 使用一個新的ConcurrentBLABLBA集合類

我的問題:
什么是最佳使用方法,因為cricital部分通常不包含大量操作(主要是添加/刪除/插入或從列表中獲取元素)?
你能推薦另一種方法,上面未列出嗎?

有些限制
- 最佳性能至關重要,內存使用不是那么多
-it必須是有序列表(當前在ArrayList上同步),盡管不是排序列表(即不使用Comparable或Comparator排序,但根據插入順序排序)
- 列表很大,包含多達100000個域對象,因此使用像CopyOnWriteArrayList這樣的東西是不可行的
- 寫入/更新電路部分通常非常快,做簡單的添加/刪除/插入或替換(設置​​)
- 大多數時候,讀操作主要執行elementAt(索引)調用,盡管某些讀操作可能會進行二分查找,或者indexOf(元素)
- 盡管像indexOf(..)之類的操作將遍歷列表,但不會對列表進行直接迭代

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

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

閱讀線程在做什么? 如果他們在列表上進行迭代,那么你真的需要確保在整個迭代過程中沒有人接觸到列表,否則你會得到非常奇怪的結果。

如果您可以准確定義所需的語義,則應該可以解決問題 - 但您可能會發現需要編寫自己的集合類型才能正確有效地執行此操作。 或者, CopyOnWriteArrayList可能足夠好 - 如果可能很昂貴的話。 基本上,您可以越多地限制您的要求,它就越有效率。

我不知道這是否是這個問題的可行解決方案,但是...我使用數據庫管理器來保存大量數據並讓它管理事務是有意義的

我是第二個Telcontar對數據庫的建議 ,因為它們實際上是為管理這種規模的數據和在線程之間進行協商而設計的,而內存中的集合則不是。

您說數據位於服務器上的數據庫中,客戶端上的本地列表是出於用戶界面的原因。 您不需要立即在客戶端上保留所有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 ();
}

顯然,你必須為自己填寫很多細節。

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

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... 

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM