1

我根据此页面上的示例制作了一个线程池。在工作线程中,我们有一个永远不会让线程死亡的无限循环,以及当没有工作要做时暂停线程的 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();可以抛出一个 RuntimeException 。NoSuchElementException并且当在该行上引发此类异常时,当前持有互斥锁的线程将死亡,并且池会泄漏线程。当线程死亡时,互斥锁似乎被释放。

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

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

我的问题:

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

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

EDIT
Java Concurrency in Practice 状态:

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

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

4

3 回答 3

4

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

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

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

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


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

于 2013-02-08T06:37:43.787 回答
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 的情况是它被同时修改,如果对队列的所有访问都是在同步块中进行的,这是不可能的。

因此,找到您访问队列的位置而不持有监视器上的锁,您将解决您的问题。

The reason why you must call wait within a loop is that wait might wake spuriously (i.e. because wait wakes up does not mean your condition has become true so you need to test it again).


As a side note, if you used a BlockingQueue you would not have to worry about those low level details and you could simply write:

while(true) {
    Runnable r = queue.take(); //blocks until queue is not empty
}
于 2013-02-10T09:53:25.603 回答
0

加入的东西java.util.concurrent给你更多的灵活性,比如定时等待和尝试锁定/获取方法。此外,与 lock.lock()/unlock() 对不同,同步为您提供代码块。当没有争用时,它也往往更有效。无论如何,在使用并发时,一定要考虑一下,java.util.concurrent因为那里已经解决了许多问题。

于 2013-02-08T14:26:45.233 回答