为可索引的并发跳过列表实现交换方法

时间:2013-05-13 19:37:04

标签: java multithreading algorithm data-structures skip-lists

我正在实现基于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实现的东西,除非没有潜在的瓶颈问题。

1 个答案:

答案 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(除非已经执行了UpdateNodeNode,因此AtomicBoolean充当锁定,并依赖ThreadPoolExecutor使得创建数千个runnables成本低廉。