代码之家  ›  专栏  ›  技术社区  ›  Herman Lintvelt

在Java6中使用并发访问列表的最佳方法

  •  7
  • Herman Lintvelt  · 技术社区  · 16 年前

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

    现在有几种方法可以使对该列表的访问线程安全:
    -使用同步块
    -使用普通 (即读写操作共享同一锁)
    -使用
    -使用一个新的 布拉布尔巴酒店

    我的问题:
    考虑到关键部分通常不包含大量操作(主要是添加/删除/插入或从列表中获取元素),最佳使用方法是什么?
    你能推荐另一种方法吗,上面没有列出?

    一些限制
    -最佳性能是至关重要的,内存使用率不是很高
    -它必须是一个有序列表(当前正在 ),虽然不是一个排序列表(即不是使用可比或比较器排序,而是根据插入顺序排序)

    -写入/更新电路部分通常非常快速,只需执行简单的添加/删除/插入或替换(设置)
    -虽然有些读取操作可能会执行二进制搜索或indexOf(element),但大多数情况下,读取操作主要执行elementAt(index)调用
    -虽然像indexOf(..)这样的操作将遍历列表,但不会对列表进行直接迭代

    5 回复  |  直到 16 年前
        1
  •  3
  •   C. K. Young    16 年前

    您必须使用顺序列表吗?如果贴图类型结构更合适,则可以使用 ConcurrentHashMap . 有一个列表,一个 ReadWriteLock 这可能是最有效的方法。

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

        2
  •  1
  •   Jon Skeet    16 年前

    如果您可以精确地定义所需的语义,那么应该可以解决这个问题——但您可能会发现,您需要编写自己的集合类型来正确有效地完成这项工作。或者, CopyOnWriteArrayList

        3
  •  1
  •   Telcontar    16 年前

    我不知道这是否是解决这个问题的可行办法,但是。。。对我来说,使用数据库管理器保存大量数据并让它管理事务是有意义的

        4
  •  1
  •   Community CDub    8 年前

    我支持 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 ();
    }
    

    显然,有很多细节需要你自己去填写。

        5
  •  1
  •   joefitz joefitz    16 年前

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

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