在Java 6中使用的最佳方法,可以同时访问List

时间:2008-10-16 08:52:26

标签: java collections concurrency

我有一个由多个线程访问的List对象。主要有一个线程,在某些情况下有两个线程,用于更新列表。根据正在处理的用户请求数,有一到五个可以从此列表中读取的线程。 该列表不是要执行的任务队列,它是正在检索和同时更新的域对象的列表。

现在有几种方法可以访问此列表的线程安全:
- 使用同步块
- 使用普通的(即读写操作共享相同的锁)
-use ReadWriteLock
- 使用一个新的 ConcurrentBLABLBA 集合类

我的问题:
什么是最佳使用方法,因为cricital部分通常不包含大量操作(大多数只是添加/删除/插入或从列表中获取元素)?
你能推荐另一种方法,上面未列出吗?

有些约束
- 最佳性能至关重要,内存使用不是那么多 -it必须是有序列表(当前正在 ArrayList 上同步),尽管不是排序列表(即不使用Comparable或Comparator排序,而是根据插入顺序排序)
- 列表很大,包含多达100000个域对象,因此使用像CopyOnWriteArrayList这样的东西是不可行的 - 写入/更新电路部分通常非常快,做简单的添加/删除/插入或替换(设置​​)
- 大多数时候,读取操作主要执行elementAt(索引)调用,尽管某些读取操作可能会执行二分查找,或者indexOf(元素)
- 尽管像indexOf(..)这样的操作将遍历列表

,但不会对列表进行直接迭代

5 个答案:

答案 0 :(得分:3)

您是否必须使用顺序列表?如果地图类型结构更合适,您可以使用ConcurrentHashMap。使用列表,ReadWriteLock可能是最有效的方法。

编辑以反映OP的编辑:对插入订单进行二进制搜索?在二进制搜索中,您是否存储时间戳并将其用于比较?如果是这样,您可以使用时间戳作为密钥,ConcurrentSkipListMap作为容器(维护密钥顺序)。

答案 1 :(得分:1)

阅读线程在做什么?如果他们在列表上进行迭代,那么你真的需要确保在整个迭代过程中没有人接触到列表,否则你会得到非常奇怪的结果。

如果您可以准确定义所需的语义,则应该可以解决问题 - 但您可能会发现需要编写自己的集合类型才能正确有效地执行此操作。或者,CopyOnWriteArrayList可能足够好 - 如果可能很昂贵的话。基本上,您可以越多地限制您的要求,它就越有效率。

答案 2 :(得分:1)

我不知道这是否是问题的可能解决方案,但是......使用数据库管理器来保存大量数据并让它管理交易是有意义的

答案 3 :(得分: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 ();
}

显然,你必须为自己填写很多细节。

答案 4 :(得分: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... 

这是一个简单的解决方案。在尝试使用更复杂的解决方案之前,我会尝试这个。