【问题标题】:High performance, lock free Java collection with very specific requirements具有非常特殊要求的高性能、无锁 Java 集合
【发布时间】:2015-06-10 02:22:47
【问题描述】:

对于具有以下要求的高性能并发收集,什么是合适的候选者:

  1. 集合中的元素数量很少(少数元素,通常少于 10 个)并且很少更改。
  2. 主要用例是遍历元素。这种情况经常发生,而且必须非常快(即应该是无锁的)。
  3. 有时会使用迭代器 remove() 方法删除元素。这最好也工作得非常快(但它不如迭代器 next() 方法重要)。
  4. 元素的顺序无关紧要,因此元素如何插入到集合中并不重要。
  5. 最好来自标准 Java 库。

我考虑过为此使用 ConcurrentLinkedQueue,但如果您从不调用 poll() 方法,就会发现它会泄漏内存(按设计)。我不确定情况是否仍然如此(提到这一点的帖子是从 ~ 2011 年开始的,我发现一些提到这可能已经解决)。

我也考虑过ConcurrentSkipListSet,但不确定排序对性能有什么影响(因为我不关心顺序)。

【问题讨论】:

  • 如果您的主要用例是查找元素,HashMap 将是最好的。至于 CopyOnWriteArrayList,如 javadoc:不支持对迭代器本身的元素更改操作(删除、设置和添加)。这些方法抛出 UnsupportedOperationException。
  • 除了使用不支持的迭代器删除元素外, CopyOnWriteArrayList 在我看来就像您要查找的内容。即使在迭代期间,您仍然可以直接调用列表的 remove() 方法。但是你不能保证你正在删除正确的元素。您是否尝试过简单地在 ArrayList 上进行同步?你证明它不够快吗?
  • +1 到 CopyOnWriteArrayList,提供“少量元素”和“很少更改”是您可以做出的假设。如果他们不是——例如,如果恶意用户可以导致有很多元素和频繁更新——那么我会避免它,因为所述恶意用户可以进行 DOS 风格的攻击(创建一个列表包含数千个元素,然后频繁地从中添加和删除元素,每个操作都是 O(n))。
  • ConcurrentHashMap 应该提供良好的性能,特别是如果您将optimal init params 用于您的用例。
  • @JFPicard 的主要用例是遍历元素并对每个元素执行操作——而不是查找特定元素。

标签: java performance data-structures collections concurrency


【解决方案1】:

如果您正在寻找“足够快”的解决方案,您可以使用 ConcurrentLinkedQueue。 Iterator.remove() 中众所周知的内存泄漏问题已作为http://bugs.java.com/view_bug.do?bug_id=6785442 的一部分得到修复。现在删除的 ConcurrentLinkedQueue$Node 应该可以成功 GC。但是,如果您正在寻找性能最高的解决方案,那么...

  1. 根本不要使用迭代器(因此,对于 Collection 的 for-each),因为 Collection.iterator() 准备了 Iterator 的新实例,顺便说一句,它的 next() 方法不是免费的: ) 每次使用 Iterator 迭代集合时,您至少花费 CPU 时间:大约 10 条为新对象分配内存的指令 + 一些构造函数指令(参见 ConcurrentLinkedQueue$Itr 的来源) + ConcurrentLinkedQueue$Itr.next( ) + 在次要 GC 上从 Eden 中删除对象。

  2. 普通数组+直接索引是最快的迭代技术。因此,使用 CopyOnWriteArrayList 或实现您自己的集合以使用普通数组来迭代多个项目。例如,如果您很少添加/删除项目,并且您希望在迭代时删除它们而不是按索引删除,您可以尝试以下操作:

    public enum IterationResult {
        NEXT, REMOVE, BREAK;
    }
    
    public interface CollectionIterator<T> {
        IterationResult onObject(T object);
    }
    
    public interface CollectionModification<T> {
        CollectionModification<T> add(T item);
        CollectionModification<T> remove(T item);
    }
    
    public class MyCollection<T> {            
        private volatile State          state;
        private final ReentrantLock     modificationLock = new ReentrantLock();
        private State                   currentModification;
    
        public MyCollection() {
            this(10);
        }
    
        public MyCollection(int initialSize) {
            state = new State(initialSize);
        }
    
        public CollectionModification<T> startModification() {
            modificationLock.lock();                
            currentModification = new State(state);
            return currentModification;
        }
    
        public void finishModification() {
            state = currentModification;
            modificationLock.unlock();
        }
    
        @SuppressWarnings("unchecked")
        public void iterate(CollectionIterator<T> it) {
            final State currentState = state;
    
            State modifiedState = null;
            try {
                out_:
                for (int i = 0; i < currentState.size; i++) {
                    final T item = (T) currentState.items[i]; // unchecked
                    final IterationResult result = it.onObject(item);
                    switch (result) {
                        case BREAK:
                            break out_;
                        case REMOVE:
                            if (modifiedState == null) {                                    
                                modifiedState = (State) startModification();                                                                        
                            }
                            modifiedState.removeExactly(item);                                
                            break;
                        default:
                            break;
                    }
                }
            } finally {
                if (modifiedState != null) {
                    finishModification();
                }
            }
        }
    
        private class State implements CollectionModification<T> {
            private Object[]            items;
            private int                 size;
    
            private State(int initialSize) {
                items = new Object[initialSize];
            }
    
            private State(State from) {
                items = new Object[from.items.length];
                size = from.size;
                System.arraycopy(from.items, 0, items, 0, size);
            }
    
            @Override
            public CollectionModification<T> add(T item) {
                if (size == items.length) {
                    final Object[] newItems = new Object[size + size >>> 1];
                    System.arraycopy(items, 0, newItems, 0, size);
                    items = newItems;
                }
    
                items[size] = item;
    
                size++;
    
                return this;
            }
    
            @Override
            public CollectionModification<T> remove(T item) {
                for (int i = 0; i < size; i++) {
                    if (Objects.equals(item, items[i])) {
                        removeItem(i);
                        break;
                    }
                }                    
                return this;
            }                
    
            private void removeExactly(T item) {
                for (int i = 0; i < size; i++) {
                    if (item == items[i]) {
                        removeItem(i);
                        break;
                    }
                }                    
            }                
    
            private void removeItem(int index) {
                if (index < items.length - 1) {
                    System.arraycopy(items, index + 1, items, index, size - 1);
                }
                size--;
            }
        }            
    }
    

用法:

    CollectionIterator<Integer> remove2 = new CollectionIterator<Integer>() {
        @Override
        public IterationResult onObject(Integer object) {
            return object == 2 ? IterationResult.REMOVE : IterationResult.NEXT;
        }
    };

    MyCollection<Integer> col = new MyCollection<>();

    CollectionModification<Integer> mod = col.startModification();
    try {
        mod.add(new Integer(1))
                .add(new Integer(2))
                .add(new Integer(3));
    } finally {
        col.finishModification();
    }

    col.iterate(remove2);

这与 CopyOnWriteArrayList 非常相似。顺便说一句,如果你只有一个修改集合的线程(单个作者)和许多读者,你可以摆脱锁定,因为volatile 足以保证作者和所有读者之间所有更改的可见性。此外,如果延迟对您很重要,您可以用忙等待来获取无锁集合来替换经典锁。

您应该了解的主要内容是,在许多情况下,针对非常具体的要求的最高效的解决方案是编写一段您自己的微调代码。这就是不为你不真正使用的东西付费的方式。这就是为什么高性能/低延迟应用程序通常不在其主要路径中使用常见的 3rd 方库

【讨论】:

  • 感谢您的详尽回答,很明显您投入了时间和思考! :) 我的注意力暂时被转移到了其他问题上,但我非常感激,并且一旦我能回到这个问题上,我也会在接下来的几天里投入时间和思考来研究它。
【解决方案2】:

我进行了一些测试。这不是一个完美的测试,但它给出了性能差异的概念。

程序:

import java.util.*;
import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) {

        testCollection(new ArrayList<Integer>());
        testCollection(Collections.synchronizedList(new ArrayList<Integer>()));
        testCollection(new CopyOnWriteArrayList<Integer>());
        testCollection(new LinkedList<Integer>());
        testCollection(Collections.synchronizedList(new LinkedList<Integer>()));
        testCollection(Collections.newSetFromMap(new ConcurrentHashMap<Integer, Boolean>()));
        testCollection(new ConcurrentLinkedQueue<Integer>());
        testCollection(new ConcurrentSkipListSet<Integer>());
    }

    static void testCollection(Collection<Integer> collection) {
        testCollection(collection, 3);
    }

    static void testCollection(Collection<Integer> collection, int count) {

        Test t = new Test(collection);

        for (int i = 0; i < 10; i++)
            System.gc();

        while (count > 0) {
            long time = 0, iterationTime;

            for (int x = 0; x < 1010; x++) {
                iterationTime = t.timeIteration();
                if (x >= 10) { // skip first 10 runs results to reduce the effect of runtime optimizations 
                    time += iterationTime;
                }
            }

            System.out.println(collection.getClass() + ": " + time / 1000000.0 + " milliseconds");
            count--;
        }
    }

    static class Test {

        private Collection<Integer> list;

        public Test(Collection<Integer> list) {
            list.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
            this.list = list;
        }

        long timeIteration() {
            Iterator<Integer> iterator;
            long start = System.nanoTime();
            for (int i = 0; i < 10000; i++) {
                for (iterator = list.iterator(); iterator.hasNext(); ) {
                    Integer x = iterator.next();
                    if (x > 20)
                        System.out.println("this doesn't happen");
                }
            }

            return System.nanoTime() - start;
        }
    }
}

结果:(为清晰起见格式化)

╔══════════════════════════════════════════╦══════════════╗
║            class (java.util.)            ║ milliseconds ║
╠══════════════════════════════════════════╬══════════════╣
║ ArrayList                                ║ 138.242208   ║
║------------------------------------------║--------------║
║ ArrayList                                ║ 135.795334   ║
║------------------------------------------║--------------║
║ ArrayList                                ║ 160.516023   ║
║------------------------------------------║--------------║
║ Collections$SynchronizedRandomAccessList ║ 371.29873    ║
║------------------------------------------║--------------║
║ Collections$SynchronizedRandomAccessList ║ 318.442416   ║
║------------------------------------------║--------------║
║ Collections$SynchronizedRandomAccessList ║ 335.079316   ║
║------------------------------------------║--------------║
║ concurrent.CopyOnWriteArrayList          ║ 299.203427   ║
║------------------------------------------║--------------║
║ concurrent.CopyOnWriteArrayList          ║ 361.800762   ║
║------------------------------------------║--------------║
║ concurrent.CopyOnWriteArrayList          ║ 316.672923   ║
║------------------------------------------║--------------║
║ LinkedList                               ║ 776.843317   ║
║------------------------------------------║--------------║
║ LinkedList                               ║ 807.458514   ║
║------------------------------------------║--------------║
║ LinkedList                               ║ 793.940155   ║
║------------------------------------------║--------------║
║ Collections$SynchronizedList             ║ 793.125156   ║
║------------------------------------------║--------------║
║ Collections$SynchronizedList             ║ 782.003326   ║
║------------------------------------------║--------------║
║ Collections$SynchronizedList             ║ 790.937425   ║
║------------------------------------------║--------------║
║ Collections$SetFromMap                   ║ 1452.049046  ║
║------------------------------------------║--------------║
║ Collections$SetFromMap                   ║ 1457.275127  ║
║------------------------------------------║--------------║
║ Collections$SetFromMap                   ║ 1450.322531  ║
║------------------------------------------║--------------║
║ concurrent.ConcurrentLinkedQueue         ║ 976.803439   ║
║------------------------------------------║--------------║
║ concurrent.ConcurrentLinkedQueue         ║ 855.64423    ║
║------------------------------------------║--------------║
║ concurrent.ConcurrentLinkedQueue         ║ 845.05258    ║
║------------------------------------------║--------------║
║ concurrent.ConcurrentSkipListSet         ║ 926.423234   ║
║------------------------------------------║--------------║
║ concurrent.ConcurrentSkipListSet         ║ 924.370203   ║
║------------------------------------------║--------------║
║ concurrent.ConcurrentSkipListSet         ║ 933.504106   ║
╚══════════════════════════════════════════╩══════════════╝

欢迎评论。

【讨论】:

  • 我已对您的表格进行了格式化以便于阅读。如果你愿意,你可以编辑你的答案:pastebin.com/uheN0N9y
  • 这在不理解代码的情况下是没有用的,因为每个项目出现三次。如果这些用于单独的操作,则表需要重新格式化。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多