在Java6中使用并发访问列表的最佳方法
我有一个列表对象被多个线程访问。更新列表的线程主要是一个线程,在某些情况下是两个线程。根据正在处理的用户请求的数量,可以从该列表中读取一到五个线程。 该列表不是要执行的任务队列,而是同时检索和更新的域对象的列表 现在有几种方法可以使对该列表的访问线程安全:在Java6中使用并发访问列表的最佳方法,java,collections,concurrency,Java,Collections,Concurrency,我有一个列表对象被多个线程访问。更新列表的线程主要是一个线程,在某些情况下是两个线程。根据正在处理的用户请求的数量,可以从该列表中读取一到五个线程。 该列表不是要执行的任务队列,而是同时检索和更新的域对象的列表 现在有几种方法可以使对该列表的访问线程安全: -使用同步块 -使用普通锁(即读写操作共享同一锁) -使用读写锁 -使用一个新的ConcurrentBLABLBA集合类 我的问题: 考虑到关键部分通常不包含大量操作(主要是添加/删除/插入或从列表中获取元素),最佳使用方法是什么? 你能推荐
-使用同步块
-使用普通锁(即读写操作共享同一锁)
-使用读写锁
-使用一个新的ConcurrentBLABLBA集合类 我的问题:
考虑到关键部分通常不包含大量操作(主要是添加/删除/插入或从列表中获取元素),最佳使用方法是什么?
你能推荐另一种方法吗,上面没有列出 一些约束
-最佳性能至关重要,内存使用率不太高
-它必须是一个有序列表(当前在ArrayList上同步),但不是一个排序列表(即,不是使用Comparable或Comparator排序,而是根据插入顺序排序)
-列表将很大,最多包含100000个域对象,因此使用CopyOnWriteArrayList之类的东西是不可行的
-写入/更新电路部分通常非常快速,只需执行简单的添加/删除/插入或替换(设置)
-虽然有些读取操作可能会进行二进制搜索,或者indexOf(element)
-虽然像indexOf(..)这样的操作将遍历列表,但没有对列表进行直接迭代,读取线程在做什么?如果他们对列表进行迭代,那么您确实需要确保在整个迭代过程中没有人接触列表,否则您可能会得到非常奇怪的结果
如果您可以精确地定义所需的语义,那么应该可以解决这个问题——但您可能会发现,您需要编写自己的集合类型来正确有效地完成这项工作。或者,如果可能很贵,也可能足够好。基本上,你越能约束你的需求,它就越有效率。你必须使用顺序列表吗?如果映射类型结构更合适,则可以使用
ConcurrentHashMap
。对于列表,读写锁可能是最有效的方法
编辑以反映OP的编辑:插入顺序上的二进制搜索?在二进制搜索中,是否存储时间戳并将其用于比较?如果是这样,您可能可以使用时间戳作为密钥,使用ConcurrentSkipListMap作为容器(用于维护密钥顺序)。我不知道这是否是解决此问题的可行方案,但是。。。对我来说,使用数据库管理器来保存大量数据并让它管理数据库中的事务是有意义的,因为它们实际上是为管理这种规模的数据和线程之间的协商而设计的,而内存中的集合则不是 您说数据位于服务器上的数据库中,而客户端上的本地列表是为了用户界面。您不需要一次在客户机上保存所有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 ();
}
classservercacheviewthingy{
专用静态最终整数可接受_尺寸=500;
私有int viewStart,viewLength;
最终地图项目
=新HashMap(1000);
最终ConcurrentLinkedQueue回调队列
=新的ConcurrentLinkedQueue();
public void getRecords(int start、int length、viewReceiver){
//记住当前视图,以防止
//此视图不会被意外修剪。
viewStart=开始;
viewLenght=长度;
//如果所选区域尚未加载,请发送请求
//加载该区域
如果(!rangeLoaded(开始,长度))
addLoadRequest(开始、长度);
//将接收器添加到队列中,以便对其进行处理
//当数据到达时
如果(接收器!=null)
添加(新回调(开始、长度、接收));
}
类回调{
int启动;
整数长度;
视图接收器接收器;
...
}
类EditorThread扩展线程{
私有空修剪(){
如果(items.size()您可以使用
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...