Java 延迟锁定和索引并发skiplist

Java 延迟锁定和索引并发skiplist,java,multithreading,skip-lists,Java,Multithreading,Skip Lists,我实现了一个延迟锁定的跳过列表,很像《多处理器编程的艺术》一书中的内容。但是我想增加skip列表,以便通过索引访问它的元素。我已经编写了这个类(它可以直接运行,只需复制/粘贴),但是我的索引访问在多线程模式下仍然失败 编辑1: 事实上,这是一个正在运行的版本!但是我必须将所有级别锁定到MAX\u LEVEL,而不是当前节点toplevel。因此,如果我能摆脱toplevel和MAX\u LEVEL之间的锁,我会非常高兴 import java.util.HashSet; import java.

我实现了一个延迟锁定的跳过列表,很像《多处理器编程的艺术》一书中的内容。但是我想增加skip列表,以便通过索引访问它的元素。我已经编写了这个类(它可以直接运行,只需复制/粘贴),但是我的索引访问在多线程模式下仍然失败

编辑1:

事实上,这是一个正在运行的版本!但是我必须将所有级别锁定到
MAX\u LEVEL
,而不是当前节点
toplevel
。因此,如果我能摆脱
toplevel
MAX\u LEVEL
之间的锁,我会非常高兴

import java.util.HashSet;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

public class IndexedSkipList10<T extends Comparable<? super T>> {
    private static class SkipNode<T extends Comparable<? super T>> {
        final ReentrantLock lock = new ReentrantLock();
        private final T data;
        private final SkipNode<T>[] next;
        private final SkipNode<T>[] prev;
        private final AtomicInteger[] span;

        private volatile boolean marked = false;
        private volatile boolean fullyLinked = false;

        public SkipNode (T data, int level) {
            this.data = data;
            next = new SkipNode[level];
            prev = new SkipNode[level];
            span = new AtomicInteger[level];
            for (int i=0; i<span.length; i++) span[i] = new AtomicInteger(1);
        }

        public void lock() {
            lock.lock();
        }

        public void unlock() {
            lock.unlock();
        }

        @Override
        public String toString() {
            return data == null ? "HEAD" : data.toString();
        }
    }

    private class SearchResult {
        private final SkipNode<T>[] preds, succs;
        private final int[] index;
        private final int[] predSpans;
        private final int ind, foundLevel;

        public SearchResult(SkipNode<T>[] preds, SkipNode<T>[] succs, int[] index, int ind, int foundLevel, int[] predSpans) {
            this.preds = preds;
            this.succs = succs;
            this.index = index;
            this.predSpans = predSpans;
            this.ind = ind;
            this.foundLevel = foundLevel;
        }
    }

    /* Global vars */
    private SkipNode<T> head;
    private AtomicInteger size = new AtomicInteger(0);
    private static final double P = 0.5; //Pugh's skiplist cookbook.
    private final int MAX_LEVEL;
    public final Random random = new Random();

    public IndexedSkipList10(int level) {
        this.MAX_LEVEL = level;

        head = new SkipNode<T>(null, level);

        // set head pointers and distance
        for(int i = 0; i < level; i++) {
            head.next[i] = head;
            head.prev[i] = head;
            head.span[i].set(1);
        }

        // set fully linked
        head.fullyLinked = true;
    }

    private SearchResult find2(T key, int topLevel) {
        SkipNode<T>[] preds = new SkipNode[MAX_LEVEL];
        SkipNode<T>[] succs = new SkipNode[MAX_LEVEL];
        int[] predSpans = new int[MAX_LEVEL];
        int[] index = new int[topLevel];
        SkipNode<T> curr, pred = head;
        int ind = 0, foundLevel = -1;

        for(int i = MAX_LEVEL - 1; i >= 0; i--) {
            curr = pred.next[i];
            while(curr != head && curr.data.compareTo(key) < 0) {
                if (i < topLevel) ind += pred.span[i].get();
                pred = curr;
                curr = pred.next[i];
            }

            if (foundLevel == -1 && curr.data != null && curr.data.equals(key))
                foundLevel = i;

            predSpans[i] = pred.span[i].get();
            preds[i] = pred;
            succs[i] = curr;
            if (i < topLevel) index[i] = ind;
        }

        return new SearchResult(preds, succs, index, ind, foundLevel, predSpans);
    }

    public boolean insert(T value) {
        if(value == null)
            return false;

        final int topLevel = randomLevel();
        SkipNode<T>[] succs, preds;
        int[] predSpans, indexes;
        SkipNode succ, pred;

        entry: while (true) {
            //SearchResult found = findOLD(value);
            SearchResult found = find2(value, topLevel);
            preds = found.preds;
            succs = found.succs;
            predSpans = found.predSpans;

            if (found.foundLevel != -1) {
                // this exact same element (key) is already in the list, we need to check if it is not being removed
                // and wait until it is fully linked
                if (!preds[found.foundLevel].marked) {
                    while (!preds[found.foundLevel].fullyLinked) {
                    }

                    //TODO: in case of Map.Entry preds the value here
                    return false;
                }

                continue;
            }

            int highestLocked = -1;
            try {
                boolean valid = true;

                // lock all preds
                // FIXME I would like to get rid of the locks i > toplevel
                for (int level = 0; valid && (level < MAX_LEVEL); level++) {
                    pred = preds[level];
                    succ = succs[level];

                    pred.lock();
                    highestLocked = level;

                    valid = !pred.marked && !succ.marked && pred.next[level] == succ && pred.span[level].get() == predSpans[level];
                }

                if (!valid) continue;

                // Link Nodes
                SkipNode nwNode = new SkipNode<T>(value, topLevel);
                for (int i = 0; i < topLevel; i++) {
                    nwNode.next[i] = succs[i];
                    preds[i].next[i] = nwNode;

                    // Set Backlink
                    nwNode.prev[i] = preds[i];
                    nwNode.next[i].prev[i] = nwNode;
                }

                // calculate spans
                for (int i = 0; i < MAX_LEVEL; i++) {
                    if (i >= topLevel) {
                        preds[i].span[i].incrementAndGet();
                    } else if (i > 0) {
                        nwNode.span[i].set(found.index[i] + preds[i].span[i].get() - found.ind);
                        preds[i].span[i].set(found.ind + 1 - found.index[i]);
                    } else {
                        // lowest node spans are always one, we have initilized the node span beeing one so nothing to do here!
                    }
                }

                // successful add linearization point
                nwNode.fullyLinked = true;
                size.incrementAndGet();

                return true;
            } finally {
                for (int level = 0; level <= highestLocked; level++)
                    preds[level].unlock();
            }
        }
    }

    protected int randomLevel() {

        int x = random.nextInt() | 0x100; //256
        x ^= x << 13;
        x ^= x >>> 17;
        x ^= x << 5;
        if ((x & 0x8001) != 0) // test highest and lowest bits
            return 1;
        int level = 1;

        while (((x >>>= 1) & 1) != 0) ++level;
        return Math.min(level+1, MAX_LEVEL-1);
    }

    private SkipNode<T> searchByIndex(int index) {
        if(index > size.get())
            return null;

        SkipNode<T> curr = head;
        int ind = -1;
        for (int i = MAX_LEVEL - 1; i >= 0; i--) {
            while (ind + curr.span[i].get() <= index) {
                ind += curr.span[i].get();
                curr = curr.next[i];
            }
        }

        return curr;
    }

    public Iterator<T> iterator() {
        return new Iterator<T>() {
            SkipNode n = head;

            @Override
            public boolean hasNext() {
                return n.next[0].data != null;
            }

            @Override
            public T next() {
                return (T) (n = n.next[0]).data;
            }
        };
    }

    public Iterator<T> iteratorDesc() {
        return new Iterator<T>() {
            SkipNode n = head;

            @Override
            public boolean hasNext() {
                return n.prev[0].data != null;
            }

            @Override
            public T next() {
                return (T) (n = n.prev[0]).data;
            }
        };
    }

    public int size() {
        return size.get();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("[");
        Iterator<T> it = iterator();
        while (it.hasNext())
            sb.append(it.next()).append(", ");

        if (sb.length() > 2)
            sb.setLength(sb.length()-2);

        return sb.append("]").toString();
    }


    public static void main(String[] args) throws InterruptedException {
        int[] threads = new int[]{1, 5, 10, 15, 25, 50, 75, 100, 200, 500, 1000};
        IndexedSkipList10<Integer> b = new IndexedSkipList10<>(5);
        b.insert(12);
        b.insert(11);
        b.insert(15);
        b.insert(16);
        b.insert(13);
        b.insert(10);
        b.insert(19);
        b.insert(18);

        System.out.println(b.searchByIndex(2));
        System.out.println(b.searchByIndex(5));

        for (int threadCount : threads) {
            System.out.println("\n----\ntesting with " + threadCount + " threads!");
            ExecutorService es = Executors.newFixedThreadPool(threadCount);
            IndexedSkipList10<Integer> a = new IndexedSkipList10<>(32);
            ConcurrentSkipListSet<Integer> controll = new ConcurrentSkipListSet<>();

            for (int i = 0; i < 50000; i++) {
                es.execute(() -> {
                    int rnd = new Random().nextInt();

                    a.insert(rnd);
                    controll.add(rnd);
                });
            }

            es.shutdown();
            es.awaitTermination(1L, TimeUnit.DAYS);

            System.out.println(controll);
            System.out.println(a);


            int cnt = 0;
            Set<Integer> uniques = new HashSet<>();
            Iterator<Integer> itSize = a.iterator();
            while (itSize.hasNext()) {
                int i = itSize.next();
                cnt++;
                if (!uniques.add(i))
                    throw new RuntimeException("Duplicate Element: " + i);
            }

            System.out.println("\nTest Size");
            if (a.size() != cnt)
                throw new RuntimeException("Size not equal! exepected: " + cnt + " got: " + a.size());

            if (a.size() != controll.size())
                throw new RuntimeException("Size not equal! exepected: " + controll.size() + " got: " + a.size());

            // Controll Results
            System.out.println("\nTest iterator");
            Iterator<Integer> ita = a.iterator();
            Iterator<Integer> itControl = controll.iterator();
            int i = 0;

            while (ita.hasNext()) {
                Integer dataA = ita.next();
                Integer dataControll = itControl.next();
                Integer aByIndex = a.searchByIndex(i++).data;

                if (!dataControll.equals(dataA))
                    throw new RuntimeException("Element not equal! " + dataControll + " != " + dataA);

                // temporary disable index tests until we have fixed the "normal" skiplist concurrency
                if (!dataControll.equals(aByIndex)) throw new RuntimeException("Element by Index not equal! " + dataControll + " != " + aByIndex + " Index: " + (i-1) + " threads: " + threadCount);
            }

            if (itControl.hasNext() || itControl.hasNext())
                throw new RuntimeException("Iterators not equal");

            // Controll Results in descending mode
            System.out.println("\nTest desc iterator");
            ita = a.iteratorDesc();
            itControl = controll.descendingIterator();
            i = a.size();

            while (ita.hasNext()) {
                Integer dataA = ita.next();
                Integer dataControll = itControl.next();
                Integer aByIndex = a.searchByIndex(--i).data;

                if (!dataControll.equals(dataA))
                    throw new RuntimeException("Desc Element not equal! " + dataControll + " != " + dataA);

                //temporary disable index tests until we have fixed the "normal" skiplist concurrency
                if (!dataControll.equals(aByIndex)) throw new RuntimeException("Desc Element by Index not equal! " + dataControll + " != " + aByIndex);

            }

            if (itControl.hasNext() || ita.hasNext())
                throw new RuntimeException("Desc Iterator not equal");
        }

        System.out.println("\nFIN");
    }
}
import java.util.HashSet;
导入java.util.Iterator;
导入java.util.Random;
导入java.util.Set;
导入java.util.concurrent.ConcurrentSkipListSet;
导入java.util.concurrent.ExecutorService;
导入java.util.concurrent.Executors;
导入java.util.concurrent.TimeUnit;
导入java.util.concurrent.AtomicInteger;
导入java.util.concurrent.locks.ReentrantLock;

公共类索引skiplist10当然,您必须锁定所有级别。您的
insert
方法可能会在所有级别上修改
span
:甚至是高于自身的级别。我也有点担心你的搜索没有同步。在我看来,如果另一个线程在不合适的时间更新了链接,您的搜索可能会陷入混乱。顶级以上的所有内容都可以简单地以原子方式递增。问题是要找到正确的节点,因为preds.next[i>toplevel]现在可以不同于succ[i>toplevel]。但对于简单的增量,必须有一种CAS+refind方法。当然,您必须锁定所有级别。您的
insert
方法可能会在所有级别上修改
span
:甚至是高于自身的级别。我也有点担心你的搜索没有同步。在我看来,如果另一个线程在不合适的时间更新了链接,您的搜索可能会陷入混乱。顶级以上的所有内容都可以简单地以原子方式递增。问题是要找到正确的节点,因为preds.next[i>toplevel]现在可以不同于succ[i>toplevel]。但对于简单的增量,必须有一种CAS+refind方法。