我正在实现基于Java ConcurrentSkipListMap的并发跳过列表映射,不同之处在于我希望列表允许重复,我还希望列表为indexable(以便查找列表的第N个元素需要O(lg(n))时间,而不是标准跳过列表的O(n)时间。这些修改没有出现问题。
此外,跳过列表的键是可变的。例如,如果列表元素是整数{0,4,7},则中间元素的键可以更改为[0,7]中的任何值,而不会提示更改列表结构;如果密钥更改为(-inf,-1]或[8,+ inf),则删除该元素并重新添加以维护列表顺序。而不是将其实现为删除后跟O(lg(n))插入,我将其实现为删除,然后执行线性遍历,然后执行O(1)插入(预期运行时为O(1) - 99节点与相邻节点交换的时间的百分比。)
很少(在启动之后)插入一个全新的节点,并且永远不会删除节点(不立即重新添加);几乎所有的操作都是elementAt(i)来检索第i个索引处的元素,或者是在修改键之后交换节点的操作。
我遇到的问题是如何实现密钥修改类。从概念上讲,我想做一些像
这样的事情public class Node implements Runnable {
private int key;
private Node prev, next;
private BlockingQueue<Integer> queue;
public void update(int i) {
queue.offer(i);
}
public void run() {
while(true) {
int temp = queue.take();
temp += key;
if(prev.getKey() > temp) {
// remove node, update key to temp, perform backward linear traversal, and insert
} else if(next.getKey() < temp) {
// remove node, update key to temp, perform forward linear traveral, and insert
} else {
key = temp; // node doesn't change position
}
}
}
}
(从insert
调用的run
子方法使用CAS来处理两个节点试图同时插入同一位置的问题(类似于ConcurrentSkipListMap
的方式处理冲突插入) - 从概念上讲,这与第一个节点锁定与插入点相邻的节点相同,只是在没有冲突的情况下减少了开销。)
通过这种方式,我可以确保列表始终有序(如果密钥更新有点延迟,也可以,因为我可以确定更新将最终发生;但是,如果列表变得无序,然后事情可能会变得混乱)。问题是以这种方式实现列表将产生大量的线程,每个Node
一个(列表中有几千个节点) - 其中大多数将在任何给定的时间点阻塞,但我'我担心数千个阻塞线程仍然会导致过高的开销。
另一个选项是使update
方法同步并从Runnable
中删除Node
接口,这样就不会让Node
中的两个线程排入更新负责在其单独的线程上处理这些更新,两个线程将轮流执行Node#update
方法。问题是,这可能会造成瓶颈;如果八个不同的线程都决定一次更新同一节点,那么队列实现可以很好地扩展,但同步实现将阻止八个线程中的七个(然后将阻塞六个线程,然后五个线程等)。
所以我的问题是,除了线程数量减少之外,我将如何实现类似队列实现的东西,否则我将如何实现类似于synchronized实现的东西,除非没有潜在的瓶颈问题。
答案 0 :(得分:0)
我想我可以用ThreadPoolExecutor
解决这个问题,比如
public class Node {
private int key;
private Node prev, next;
private ConcurrentLinkedQueue<Integer> queue;
private AtomicBoolean lock = new AtomicBoolean(false);
private ThreadPoolExecutor executor;
private UpdateNode updater = new UpdateNode();
public void update(int i) {
queue.offer(i);
if(lock.compareAndSet(false, true)) {
executor.execute(updater);
}
}
private class UpdateNode implements Runnable {
public void run() {
do {
try {
int temp = key;
while(!queue.isEmpty()) {
temp += queue.poll();
}
if(prev.getKey() > temp) {
// remove node, update key to temp, perform backward linear traversal, and insert
} else if(next.getKey() < temp) {
// remove node, update key to temp, perform forward linear traveral, and insert
} else {
key = temp; // node doesn't change position
}
} finally {
lock.set(false);
}
} while (!queue.isEmpty() && lock.compareAndSet(false, true));
}
}
}
这样我就可以获得队列方法的优点而不会阻塞一千个线程 - 每次我需要更新一个节点时,我会执行一个UpdateNode
(除非已经执行了UpdateNode
在Node
,因此AtomicBoolean
充当锁定,并依赖ThreadPoolExecutor
使得创建数千个runnables成本低廉。