Java - 并发清除列表

时间:2014-03-05 15:17:39

标签: java multithreading locking copyonwritearraylist

我正在尝试找到实现以下API的好方法:

void add(Object o);
void processAndClear();

该类将存储对象,并在调用processAndClear时将迭代当前存储的对象,以某种方式处理它们,然后清除存储。这个类应该是线程安全的。

显而易见的方法是使用锁定,但我希望更“并发”。这是我将使用的方法:

class Store{
    private AtomicReference<CopyOnWriteArrayList<Object>> store = new AtomicReference<>(new CopyOnWriteArrayList <>());

    void add(Object o){
        store.get().add(o);
    }

    void processAndClear(){
        CopyOnWriteArrayList<Object> objects = store.get();
        store.compareAndSet(objects, new CopyOnWriteArrayList<>());
        for (Object object : objects) {
            //do sth
        }
    }
}

这将允许尝试添加对象的线程几乎立即继续,而不会有任何锁定/等待xlearing完成。这或多或少是正确的方法吗?

2 个答案:

答案 0 :(得分:3)

您的上述代码不是线程安全的。想象一下:

  1. 线程A在add()
  2. 之后store.get()处暂停
  3. 线程B在processAndClear()中,替换列表,处理旧列表的所有元素,然后返回。
  4. 线程A恢复并将新项目添加到永远不会被处理的现在过时的列表中。
  5. 这里最简单的解决方案是使用LinkedBlockingQueue,这样可以简化任务:

    class Store{
      final LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<>();
    
      void add(final Object o){
        queue.put(o); // blocks until there is free space in the optionally bounded queue
      }
    
      void processAndClear(){
        Object element;
        while ((element = queue.poll()) != null) { // does not block on empty list but returns null instead
          doSomething(element);
        }
      }
    }
    

    修改:如何使用synchronized

    执行此操作
    class Store{
      final LinkedList<Object> queue = new LinkedList<>(); // has to be final for synchronized to work
    
      void add(final Object o){
        synchronized(queue) { // on the queue as this is the shared object in question
          queue.add(o);
        }
      }
    
      void processAndClear() {
        final LinkedList<Object> elements = new LinkedList<>(); // temporary local list
        synchronized(queue) { // here as well, as every access needs to be properly synchronized
          elements.addAll(queue);
          queue.clear();
        }
    
        for (Object e : elements) { 
          doSomething(e); // this is thread-safe as only this thread can access these now local elements
        }
      }
    }
    

    为什么这不是一个好主意

    虽然这是线程安全的,但与并发版本相比,速度要慢得多。假设您的系统有100个线程,经常调用add,而一个线程调用processAndClear。然后会出现以下性能瓶颈:

    • 如果一个线程调用add,则其他99个在此期间被搁置。
    • processAndClear的第一部分期间,所有100个线程都被搁置。

    如果您假设那100个添加线程没有其他任何操作,您可以轻松地显示应用程序以与单线程应用程序相同的速度运行减去同步成本。这意味着:100个线程的添加速度实际上比1慢。如果您使用第一个示例中的并发列表,则情况并非如此。

    但处理线程会有轻微的性能提升,因为doSomething可以在旧元素上运行,同时添加新元素。但是,并发示例可能会更快,因为您可以让多个线程同时处理

    有效synchronized也可以使用,但是你会自动引入性能瓶颈,可能会导致应用程序以单线程运行速度变慢,迫使你进行复杂的性能测试。此外,扩展功能总是存在引入线程问题的风险,因为锁定需要手动完成。
    相反的并发列表解决了所有这些问题,无需额外的代码,代码可以在以后轻松更改或扩展。 / p>

答案 1 :(得分:1)

  

该类将存储对象,并在调用processAndClear时将迭代当前存储的对象,以某种方式处理它们,然后清除存储。

这似乎应该使用BlockingQueue来执行此任务。您的add(...)方法会添加到队列中,您的消费者会调用​​take()来阻止等待下一个项目。 BlockingQueueArrayBlockingQueue是一种典型的实现)为您处理所有同步和信令。

这意味着您不必拥有CopyOnWriteArrayListAtomicReference。您将失去的是一个集合,您可以通过其他原因进行迭代,而不是当前的发布。