2008-10-16 82 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... 

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