【问题标题】:Is synchronized enough to make the drainTo() method of a BlockingQueue atomic?是否同步足以使 BlockingQueue 的 drainTo() 方法成为原子方法?
【发布时间】:2015-06-21 12:12:32
【问题描述】:

如果我只是做这样的事情:

synchronized(taskQueue) { //taskQueue is a BlockingQueue 
  taskQueue.drainTo(tasks); //tasks is a list
}

我确定不能在同步块内执行对taskQueue.put()taskQueue.take() 的并发调用吗?

换句话说,我是否让 drainTo() 方法成为原子方法?

或者更一般地说,我如何使线程安全操作的组合原子化?

例子:

if(taskQueue.size() == 1) {
   /*Do a lot of things here, but I do not want other threads
     to change the size of the queue here with take or put*/
}
//taskQueue.size() must still be equal to 1

【问题讨论】:

    标签: java multithreading synchronization


    【解决方案1】:

    见下面Java docs of BlockingQueue的摘录

    BlockingQueue 实现是线程安全的。所有排队方法都使用内部锁或其他形式以原子方式实现其效果 并发控制。但是,批量收集操作 addAll、containsAll、retainAll 和 removeAll 不一定 除非在实现中另有说明,否则以原子方式执行。 因此,例如,addAll(c) 可能会失败(抛出 例外)在c中只添加了一些元素之后。

    此外,请查看显示 BlockingQueue 实现可以安全地用于多个生产者和多个消费者的示例。

    因此,如果您不使用像 addAll, containsAll, retainAll and removeAll 这样的批量收集操作,那么您就是线程安全的。

    您甚至不需要synchronized(taskQueue) { 并且可以直接使用taskQueue.drainTo(tasks);,因为BlockingQueue 实现是线程安全的 用于非批量收集操作,例如puttake、@ 987654328@等

    希望这会有所帮助!

    【讨论】:

    • 谢谢。一般来说,您将如何为一系列 thread.safe 操作同步整个队列(以使序列原子化)?例如,如果您想检查队列的大小是否为 0,并在队列上执行某些操作而不对其进行任何其他并发操作(例如 take 和 put)。同步够了吗?
    • 仅当您不使用 Java 的线程安全 API 时,您才需要使用同步方法和语句 (docs.oracle.com/javase/tutorial/essential/concurrency/…) 进行显式同步。在您的情况下,由于您使用的是 BlockingQueue 并且它的实现对于非批量收集操作是线程安全的,因此您不需要进行显式同步。检查我提供的链接中的示例,puttake 方法在没有任何显式同步的情况下使用。
    • BlockingQueue 有多种实现方式,例如 ArrayBlockingQueueLinkedBlockingQueue,请阅读最适合您要求的文档。
    • 我在问题的末尾添加了另一个示例。集合确实是线程安全的,但线程安全操作的组合不能是原子的。在 if 内部,是什么阻止了其他线程调用 take 或 put?谢谢!
    • @jameslarge 我同意接口和实现类之间没有明确的合同,除了我们知道的正常继承合同,但在BlockingQueue 的情况下,Java 文档中明确提到- “BlockingQueue 实现是线程安全的。”和“批量收集操作 addAll、containsAll、retainAll 和 removeAll 不一定以原子方式执行,除非在实现中另有规定”所以,我觉得不需要检查 API 代码和验证。 OP 担心非批量收集操作。
    【解决方案2】:

    以 LinkedBlockingQueue 为例,它的成员变量为 'takeLock' 和 'putLock'。

    所以客户端同步在这里没有帮助,因为其他“采取”操作不受此锁的保护,即使此锁来自队列本身。

    drainTo() 方法由“takeLock”保护,对于任何其他“take”操作,它都是线程安全的。但是对于 put 操作,它们是由 putLock 保护的,所以不会受到影响。

    所以我认为这里不需要任何东西!

    【讨论】:

    • 不确定我是否理解。我不希望在执行 drainTo() 方法时执行任何 put。
    • @Kami,如果您使用 LinkedBlockingQueue,那么只需使用不同步的 drainTo() 就足够了。看不清楚的可以看源码。
    • 我在问题的末尾添加了另一个示例。集合确实是线程安全的,但线程安全操作的组合不能是原子的。在 if 内部,是什么阻止了其他线程调用 take 或 put?谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-04
    • 2012-03-13
    • 1970-01-01
    • 1970-01-01
    • 2011-08-13
    • 1970-01-01
    • 2012-07-13
    相关资源
    最近更新 更多