【问题标题】:How can I access this list and also be thread safe?如何访问此列表并确保线程安全?
【发布时间】:2016-06-08 21:57:12
【问题描述】:

我的主线程产生 2 个线程,它们都需要访问同一个列表。我不确定最好的方法是什么。这是我所拥有的,但我仍然遇到了 concurrentModificationException。

class Parent {
   private List<String> data;

   public List<String> getData() {
      return data;
   }

   public static void main(String args[]) {
      Parent p = new Parent();
      p.start();
   }

   public void start() {
      Thread a = new Thread(new A(this)).start();
      Thread b = new Thread(new B(this)).start();
   }

   public A implements Runnable {
      private Parent parent;

      public A(Parent p) {
         parent = p;
      }

      public void run() {
         while (true) {
            parent.getData().add("data");
         }
      }
   }

   public B implements Runnable {
      private Parent parent;

      public B(Parent p) {
         parent = p;
      }

      public void run() {
         Iterator<String> i = parent.getData().iterator();
         while(i.hasNext()) {
            // do more stuff with i
            i.remove();
         }
      }
   } 
}

我的A 类基本上是数据的生产者,B 是消费者。我接受我以错误的方式解决这个问题的可能性。因此,欢迎所有帮助。我只需要能够安全地从一个线程添加到列表并从另一个线程的列表中删除一个项目。提前致谢。

【问题讨论】:

  • 不要使用列表代替队列。 java.util.concurrent 包中有大量用于生产者/消费者操作的实用程序。
  • 你可能想要一个 BlockingQueue,而不是 List。
  • 酷我看看这些。如果我换掉列表,其余的理论上应该没问题吗?

标签: java multithreading list collections


【解决方案1】:

好吧,对于生产者/消费者,我推荐LinkedBlockingQueueConcurrentLinkedQueue。这将处理并发读取和写入(或本例中的推送/轮询)。

您可能希望您的消费者一直运行,直到向其发送某些关闭条件。如果您使用的是阻塞队列,这意味着您将希望发送一个排队的项目,指示消费者应该停止消费。这将是一个带有关闭的阻塞队列实现。

   public enum QueueItemType {
      CONSUMABLE,
      SHUTDOWN
   }

   public class QueueItem {
      public final QueueItemType type;
      public final String payload;

      public QueueItem(QueueItemType type, String payload) {
         this.type = type;
         this.payload = payload;
      }
   }

   public class B implements Runnable {
      private Parent parent;

      public B(Parent p) {
         parent = p;
      }

      public void run() {
         while(true) {
            QueueItem data = parent.getData().poll();
            if (data.type == QueueItemType.SHUTDOWN) {
               break;
            } else {
               // do more stuff with data.payload
            }
         }
      }
   }

请注意,对于阻塞队列的poll 结果没有空检查。这是因为,根据定义,阻塞队列会阻塞正在运行的线程,直到有东西出现。

如果您希望有一个不与生产者竞争的消费者,那么您需要定期轮询并休眠消费者线程。如果您使用了 ConcurrentLinkedQueue,以下是一个示例:

   public class B implements Runnable {
      private Parent parent;

      public B(Parent p) {
         parent = p;
      }

      public void run() {
         while(parent.isStillRunning()) {
            String data = parent.getData().poll();
            if (data != null) {
              // do more stuff with data
            } else {
              Thread.sleep(10 /*10 ms, but you can make this whatever poll interval you want*/);
            }
         }
      }
   }

【讨论】:

  • 不要为此使用 Thread.sleep()。而是通过设置一个 volatile 标志并中断消费者线程,或者通过在队列中放置一个“毒丸”来关闭队列。否则,很好的答案。
  • 随时改进答案:)。 “毒丸”方法肯定适用于阻塞队列。 ConcurrentLinkedQueue 的优点是它在生产者和消费者之间没有争用,这是底层算法的一个非常酷的属性。所以线程轮询可能会占用一些 CPU,但生产者永远不会被消费者锁定。
  • 我要去研究这个“毒丸”,我要尽量避免睡觉
  • 我已将第一个示例更新为使用毒丸。
  • 好的我喜欢这个,它比我之前想的更简单更好。感谢@Chill 的帮助
【解决方案2】:

影响最小的更改可能是使用同步的 setter。 这样一来,线程必须等待锁被释放才能添加到集合中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多