【问题标题】:Synchronized List/Map in Java if only one thread is writing to it如果只有一个线程正在写入,Java 中的同步列表/映射
【发布时间】:2015-11-12 19:22:25
【问题描述】:

第一个线程不断地用对象填充集合。第二个线程需要遍历这些对象,但不会更改集合。

目前我使用Collection.synchronized 使其成为线程安全的,但有没有快速的方法?

更新

很简单:只要按下鼠标按钮,第一个线程(ui)就不断地将鼠标位置写入ArrayList。第二个线程(渲染)根据列表绘制一条线。

【问题讨论】:

  • 如果您在没有显式同步的情况下对其进行迭代,即使同步列表也不是线程安全的。
  • 你能找到一种方法让第二个线程只需要看到新对象吗?这样,您就可以使用阻塞队列将新数据传递给第二个线程。
  • 也许可以查看持久数据结构。结构共享比同步或copyonwrite要好得多。
  • 这听起来很像一个任务,您需要队列而不是列表,并且您将拥有 ConcurrentLinkedQueue,您无需担心锁定。
  • @jozzy,同步不仅仅是“接收最新数据”。从OP的问题来看,我们甚至不知道收集的种类,更不用说收集是如何实现的了。如果一个线程正在迭代集合,而另一个线程正在更新它,这完全有可能导致迭代线程看到集合处于无效状态,并导致程序崩溃。

标签: java multithreading collections synchronized java.util.concurrent


【解决方案1】:

使用 java.util.concurrent.ArrayBlockingQueue.ArrayBlockingQueue 实现 BlockingQueue。它完全符合您的需求。

  1. 它非常适合生产者-消费者案例,因为这是您的案例。

  2. 您还可以配置访问策略。 Javadoc 是这样解释访问策略的:

    如果为 true,则公平,则在插入或删除时阻塞的线程的队列访问将按 FIFO 顺序处理;如果为 false,则未指定访问顺序。

【讨论】:

  • 这就是我要找的那个。绝对比我链接的双端队列更好。
【解决方案2】:

即使您同步列表,它在迭代时也不一定是线程安全的,因此请确保您在其上进行同步:

synchronized(synchronizedList) {
    for (Object o : synchronizedList) {
        doSomething()
    }
}

编辑:

这是一篇关于此事的非常清楚的文章: http://java67.blogspot.com/2014/12/how-to-synchronize-arraylist-in-java.html

【讨论】:

    【解决方案3】:

    如 cmets 中所述,您需要在此列表上显式同步,因为迭代不是原子的:

    List<?> list = // ...
    

    线程 1:

    synchronized(list) {
        list.add(o);   
    }
    

    线程 2:

    synchronized(list) {
        for (Object o : list) {
            // do actions on object
        }
    }
    

    【讨论】:

    • 而且我不需要使用 Collection.synchronized...?
    • @doev 如果你只执行这两个操作,你不会
    【解决方案4】:

    目前我可以想到 3 个选项来处理 ArrayList 中的并发:-

    1. 使用 Collections.synchronizedList(list) - 当前您正在使用它。

    2. CopyOnWriteArrayList - 行为与 ArrayList 类非常相似,只是在修改列表时,不是修改底层数组,而是创建一个新数组并丢弃旧数组。它会比 1 慢。

    3. 使用 ReentrantReadWriteLock 创建自定义 ArrayList 类。您可以围绕 ArrayList 类创建一个包装器。在读取/迭代/循环时使用读锁,在数组中添加元素时使用写锁。 例如:-

      import java.util.List;
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReadWriteLock;
      import java.util.concurrent.locks.ReentrantReadWriteLock;
      
      public class ReadWriteList<E> {
      
          private final List<E> list;
          private ReadWriteLock lock = new ReentrantReadWriteLock();
          private final Lock  r =lock.readLock();
          private final Lock  w =lock.writeLock();
      
          public ReadWriteList(List<E> list){
              this.list=list;
          }
      
          public boolean add(E e){
      
              w.lock();
              try{
                  return list.add(e);
              }
              finally{
              w.unlock();
              }
          }
      
          //Do the same for other modification methods
      
          public E getElement(int index){
              r.lock();
              try{
      
                  return list.get(index);
              }
              finally{
                  r.unlock();
              }   
          }
      
          public List<E> getList(){
              r.lock();
              try{
              return list;
              }
              finally{
              r.unlock();
              }   
          }
          //Do the same for other read methods
      }
      

    【讨论】:

      【解决方案5】:

      如果您的阅读频率远高于写作频率,您可以使用CopyOnWriteArrayList

      【讨论】:

      • 在某些情况下 CopyOnWriteArrayList 肯定是一种选择。谢谢
      • OP 说,“第一个线程正在用对象连续填充集合。”这绝对是CopyOnWriteArrayList.最差 用例。将N 个元素添加到CopyOnWriteArrayList 的时间复杂度为O(N^2)。
      【解决方案6】:

      SetList 更适合您的需求吗?

      如果是这样,您可以使用Collections.newSetFromMap(new ConcurrentHashMap&lt;&gt;())

      【讨论】:

        猜你喜欢
        • 2012-08-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-04
        相关资源
        最近更新 更多