【问题标题】:Differences between synchronized keyword and ReentrantLock [duplicate]synchronized 关键字和 ReentrantLock 之间的区别 [重复]
【发布时间】:2013-02-08 05:47:48
【问题描述】:

我根据this page上的例子做了一个线程池。 在工作线程中,我们有一个永远不会让线程死亡的无限循环,以及当没有工作要做时暂停线程的 wait() 方法调用:

while (true) {
    synchronized(queue) {
         loop:while (queue.isEmpty()) { // labled the loop so we can return here
             try
             {
                 queue.wait();
                 if(queue.isEmpty())    // check the condition predicate again
                     continue loop;
             }
             catch (InterruptedException ignored)
             {
             }
         }    
         r = (Runnable) queue.removeFirst();
 }

// If we don't catch RuntimeException, 
// the pool could leak threads
try {
    r.run();
}
catch (RuntimeException e) {
    // You might want to log something here
}

事实上,如果队列为空,r = (Runnable) queue.removeFirst(); 可以抛出 NoSuchElementException,这是一个 RuntimeException。并且当在该行上抛出这样的异常时,持有互斥锁的当前线程死亡并且池泄漏线程。当线程死亡时,互斥锁似乎被释放了。

但是,如果您不使用默认的synchronized 关键字来同步队列,而是使用ReentrantLock 锁定并使用Condition 发出信号和等待,则持有互斥锁的当前线程似乎不会释放意外中断时锁定。

因此,就我而言,当我在“线程”选项卡下检查 JVisualVM 时,我可以看到 AWT-EventQueue-0 线程正在等待 Thread-1 释放互斥锁。但是 Thread-1 在运行任务的途中死了,意外终止了 (RuntumeException),而且互斥锁似乎没有被释放。

我的问题:

1) 如果持有它的线程意外终止,ReentrantLocks 不会被释放吗?

2) 上面的sn-p代码中while (queue.isEmpty()) {if (queue.isEmpty()) {有什么区别吗?我看不出有什么区别,因为线程在这两种情况下都会等待。但我认为使用if 时的行为会有所不同(例如,如果多个线程会影响队列)。

编辑
Java Concurrency in Practice 状态:

由于所有这些原因,当你从等待中醒来时,你必须 再次测试条件谓词,然后返回等待 (或失败)如果它还不是真的。既然你可以醒来 在您的条件谓词不为真的情况下反复向上,您 因此必须始终从循环中调用等待,测试 每次迭代中的条件谓词。

看看我在上面代码中的编辑,现在代码应该是正确的,如 Java Concurrency in Practice 中所述。

【问题讨论】:

  • 关于代码和忽略 InterruptedException 的注释。你打算如何停止你的申请?你使用 System.exit() 吗?
  • 第二次检查是否不需要if语句中的队列为空。您将在下一次迭代之前检查相同的条件
  • 注意:如果你希望你的线程可以被中断,请使用 lockInterruptibly()(docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…) 而不是普通的 lock()。

标签: java multithreading synchronized reentrantlock


【解决方案1】:

1) 如果持有它的线程按预期终止,ReentrantLocks 不会被释放吗?

仅当您显式调用 Lock#unlock() 时才释放锁定。这就是为什么建议在 finally 块中调用 Lock#unlock() 以防止应用程序中的死锁。

2) while (queue.isEmpty()) { 和 if 之间有什么区别吗 (queue.isEmpty()) { 在上面的代码 sn-p 中?我看不到任何 区别,因为线程在这两种情况下都会等待。但我认为 使用 if 时的行为不同(例如,如果多个线程可以 影响队列)。

在那里,在特定情况下没有太大差异。但是使用while 你保证在你的应用程序中断言,当队列为空时你不会调用removeFirst()。 此外,使用 while 代替 if 的优点是 spurious wakeups


注释: 如果您不仅要为教育实施此架构,请考虑使用BlockingQueue。 java.util.concurrent 库解决了许多多线程问题,在大多数情况下,您可以基于 java.util.concurrent 的高级抽象来构建应用程序,而不是使用诸如 wait()/notify() 之类的低级技术。

【讨论】:

  • 我明白了,但是似乎一个线程在意外中断时会在使用synchronized关键字时自动释放其锁定,但在使用ReentrantLock时不会。
  • 我从未听说过虚假唤醒。谢谢你通知我。看看我在上面的代码 sn-p 中的编辑以及我帖子末尾的 Java Concurrency in Practice 的引用。那个编辑有任何 cmets 吗?
【解决方案2】:

您的代码似乎太复杂了——我只想写:

while (true) {
    synchronized(queue) {
         while (queue.isEmpty()) {
             try {
                 queue.wait();
             } catch (InterruptedException ignored) {
                 //don't ignore me please
                 //you probably should exit the loop and return here...
             }
         }
         r = queue.removeFirst(); //Why use a cast? Use generics instead.
     }
 }

queue.removeFirst() 可能抛出 NoSuchElementException 的唯一情况是它被同时修改,如果对队列的所有访问都在同步块中进行,这是不可能的。

所以找到你访问队列的地方而不用锁在监视器上,你就会解决你的问题。

您必须在循环中调用wait 的原因是等待可能会虚假唤醒(即,等待唤醒并不意味着您的条件已变为真,因此您需要再次测试它)。


附带说明,如果您使用BlockingQueue,则不必担心那些低级细节,您可以简单地写:

while(true) {
    Runnable r = queue.take(); //blocks until queue is not empty
}

【讨论】:

  • 但是如果 wait() 可以虚假地唤醒,我不应该像上面的代码示例那样检查 wait() 之后的条件吗?不是你自己说的吗?
  • 我看到我的应用程序池中的线程在队列为空时尝试执行 queue.removeFirst() (并且队列在我的代码中仅在同步块内使用了两次) ,所以我认为它是虚假的唤醒。然后我必须再次检查wait()之后的条件,对吧?
  • @Rox 是的,您可以添加 if + continue,但由于它严格等同于简单的 while,我看不出任何添加混乱的原因。
  • 您的代码也有不平衡的大括号 - 我假设 r = queue.removeFirst(); 在同步块内。
  • 哦,好的!但是如果我使用 if 语句而不是 while 循环,那么我将在 wait() 唤醒之后使用另一个 if 语句检查条件,对吧?
【解决方案3】:

java.util.concurrent 中的内容为您提供了更多的灵活性以及定时等待和尝试锁定/获取方法等功能。此外,与 lock.lock()/unlock() 对不同,同步为您提供代码块。当没有争用时,它也往往更有效。 无论如何,在使用并发时,一定要看看java.util.concurrent,因为那里已经解决了很多问题。

【讨论】:

    猜你喜欢
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    • 2019-01-17
    • 1970-01-01
    • 2013-10-27
    • 1970-01-01
    • 2021-09-07
    • 2014-08-16
    相关资源
    最近更新 更多