synchronized关键字和ReentrantLock之间的差异

时间:2013-02-08 05:47:49

标签: java multithreading synchronized reentrantlock

我根据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)上面的代码段中while (queue.isEmpty()) {if (queue.isEmpty()) {之间是否有任何区别?我看不出任何差异,因为线程会在两种情况下都等待。但我认为使用if时行为不同(如果多个线程可能会影响队列)。

修改
Java Concurrency in Practice声明:

  

出于所有这些原因,当你从等待中醒来时,你必须这样做   再次测试条件谓词,然后再回去等待   (或失败)如果还不是真的。既然你可以醒来   没有你的条件谓词是真的,反复向上,你   因此必须始终在循环内调用wait,进行测试   每次迭代中的条件谓词。

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

3 个答案:

答案 0 :(得分:4)

  

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

仅在您明确调用Lock#unlock()时锁定释放。这就是为什么建议在finally块中调用Lock#unlock()以防止应用程序出现死锁。

  

2)while(queue.isEmpty()){和if之间是否有任何区别?   (queue.isEmpty()){在上面的代码片段中?我什么也看不见   因为线程在两种情况下都会等待。但我认为   使用if时的行为不同(比如多个线程可以   影响队列。)

在特定情况下没有大的差异。但是使用while保证在应用程序中使用assert,当Queue为空时,不会调用removeFirst()。 此外,使用while而不是if的PROS是spurious wakeups


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

答案 1 :(得分:1)

你的代码看起来太复杂了 - 我只想写:

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的唯一情况是它是否同时被修改,如果所有对队列的访问都是在synchronized块中进行的话,这是不可能的。

因此,无需在显示器上按住锁定即可找到您访问队列的位置,您将解决问题。

你必须在一个循环中调用wait的原因是等待可能是虚假唤醒(即因为等待唤醒并不意味着你的情况已经变为真,所以你需要再次测试它。)


作为旁注,如果您使用了BlockingQueue,则不必担心这些低级别的详细信息,您可以简单地写一下:

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

答案 2 :(得分:0)

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