以线程安全的方式填充映射并将该映射从后台线程传递给另一个方法?

时间:2017-02-04 07:47:38

标签: java multithreading data-structures thread-safety atomicity

我有一个以下类,多个线程将调用 add 方法以线程安全的方式填充 channelMessageHolder CHM。

在同一个班级中,我有一个每30秒运行一次的背景线程,并通过传递来自 send channelMessageHolder 方法>

public class Processor {
  private final ScheduledExecutorService executorService = Executors
      .newSingleThreadScheduledExecutor();
  private final AtomicReference<ConcurrentHashMap<Channel, ConcurrentLinkedQueue<Message>>> channelMessageHolder =
                new AtomicReference<>(new ConcurrentHashMap<Channel, ConcurrentLinkedQueue<Message>>());

  private Processor() {
    executorService.scheduleAtFixedRate(new Runnable() {
      @Override
      public void run() {
        send();
      }
    }, 0, 30, TimeUnit.SECONDS);
  }

  // this will be called by only single background thread
  private void send(ConcurrentHashMap<Channel, ConcurrentLinkedQueue<Message>> messageByChannels) {
    for(Entry<Channel, ConcurrentLinkedQueue<Message>> entry : messageByChannels.entrySet()) {
      Channel channel = entry.getKey();
      ConcurrentLinkedQueue<Message> messageHolder = entry.getValue();

      while (!messageHolder.isEmpty()) {
        Message message = messageHolder.poll();
        ....
        // process this and send to database
      }      
    }
  }

  // called by multiple threads
  public void add(final Channel channel, final Message message) {
    // populate channelMessageHolder in a thread safe way
  }
}

问题

正如您所看到channelMessageHolder类中已存在Processor所以我需要每隔30秒从此地图中显式传递数据以发送方法吗?或者我可以直接在我的发送方法中使用它?

混淆是,如果我在我的send方法中直接使用它,那么它将同时由多个线程填充,这就是为什么我使用AtomicReference的getAndSet方法将其传递给send方法。

让我知道我所做的事情是否错误,还有更好的方法吗?

2 个答案:

答案 0 :(得分:1)

  

或者我可以直接在我的send方法中使用它而不传递任何内容

您应该能够在send方法中直接使用channelMessageHolder.getAndSet(new ConcurrentHashMap<Channel, ConcurrentLinkedQueue<Message>>())方法send方法,而不会出现任何问题。

那就是说, Java 8 computeIfAbsent类中添加了一个名为ConcurrentHashMap的新方法,这意味着你并不真正需要AtomicReference你正在使用的。

答案 1 :(得分:1)

  

正如您所见,我的Processor类中已经存在channelMessageHolder所以我需要每隔30秒从此映射显式传递数据以发送方法吗?或者我可以直接在我的发送方法中使用它?

您当然可以直接在send()方法中使用它,因为AtomicReference已经同步,所以您不需要ConcurrentHashMap包装器。您需要担心的是,地图中的键和值对象正在正确同步。我假设Channel是不可变的,ConcurrentLinkedQueue是并发的,所以你应该是好的。

// no need for AtomicReference
private final ConcurrentHashMap<Channel, ConcurrentLinkedQueue<Message>> channelMessageHolder =
     new ConcurrentHashMap<Channel, ConcurrentLinkedQueue<Message>>();

ConcurrentHashMap为您处理同步,因此生产者线程可以在您的发送方线程发送项目而不发生冲突的同时将项目添加到其中。只有在尝试在多个线程之间共享不同步的类时才需要AtomicReference

  

混淆是,如果我在send方法中直接使用它,那么它将同时由多个线程填充,这就是为什么我使用AtomicReference的getAndSet方法将它传递给send方法。< / p>

没错,但没关系。多个线程将向ConcurrentLinkedQueue添加消息。您的后台线程每30秒启动一次,获取Channel,然后出列,然后发送当时队列中的消息。 ConcurrentLinkedQueue可以防止生产者和消费者的竞争状况。

您的代码中存在的问题是,它不可重入,因为它依赖于对队列的多次调用:

while (!messageHolder.isEmpty()) {
    Message message = messageHolder.poll();

它适用于你的情况,因为看起来只有一个线程出列但是下面的代码更好:

while (true) {
    // only one call to the concurrent queue
    Message message = messageHolder.poll();
    if (message == null) {
        break;
    }
    ...
}