在同一台监视器上等待的两个线程可以称为死锁吗?

时间:2016-02-17 06:33:35

标签: java multithreading deadlock

两个线程正在同一个监视器上等待,例如,如果一个线程调用wait on' lock'获取监视器的另一个线程在通知第一个线程之前也会调用wait。现在两个线程都在等待,但没有人得到通知。我怎么称呼这种情况?这可以称为僵局吗?

修改 假设这些是唯一的两个线程,并且无法从其他地方通知它们 更新:我刚刚创建了我所描述的情况。以下代码在侦听器线程之前启动更换器线程的大部分时间都可以正常工作。但是,当我在更换器之前启动监听器时,程序只会在打印两行(一个来自转换器,一个来自侦听器线程)后挂起。我在更换器之前调用监听器的情况会被称为死锁吗?

$teamTitles = array("Team Id","Team Name","Captain");
$whichteam = 5;
$sql = "SELECT teamid, teamname, captain FROM teams WHERE teamid= ?";
$teamData = DB::run($sql,[$whichteam])->fetchAll();

displayTable($teamTitles, $teamData);

function displayTable($titles, $data) {
?>
<table border='1'>
  <tr>
  <?php foreach($titles as $value)): ?>
    <th><?=$value?></th>
  <?php endforeach ?>
  </tr>
  <?php foreach($data as $row)): ?>
  <tr>
    <?php foreach($row as $value)): ?>
    <td><?=$value?></td>
    <?php endforeach ?>
  </tr>
  <?php endforeach ?>
</table>
<?
}


在监听器之前启动转换器时的输出:
    将int的值更改为:1
    收到的变更:1
    将int的值更改为:2
    收到的变更:2
    将int的值更改为:3
    收到的变更:3
    将int的值更改为:4
    收到的变更:4
    将int的值更改为:5
    收到的变更:5
程序终止。

在更换器之前启动监听器时的输出:
收到的变更:0
将int的值更改为:1
程序无法终止。

感谢。

7 个答案:

答案 0 :(得分:6)

如果这些是唯一涉及的两个线程(例如,访问监视器),那么是。如果有其他线程可以访问监视器并解锁它,那么没有。

请记住,您正在谈论两个主题 - 监视器通常是线程术语中的互斥体。但是wait是与条件变量相关联的东西,它需要互斥锁才能工作,执行更细微的任务,根据条件故意阻塞线程一个线程向另一个线程发出信号,其中一个警告称为虚假唤醒

答案 1 :(得分:4)

这不是死锁,因为有一种方法可以解决这种情况 - 如果第三个线程调用notify()notifyAll(),那么之前的两个等待线程将返回准备状态。

死锁通常无法在应用程序本身内解析,需要重启。

这就是为什么我不会把你的情况称为僵局。

还有另外两个术语描述了线程协调问题:

活锁饥饿

以下是LoveLock和Starvation的确切定义 - Starvation and LiveLock

这不是LiveLock ,因为线程不会互相响应。

你的情况可能最接近饥饿一词,但并不完全是饥饿。饥饿是指线程等待很长时间的资源。在您的情况下,资源是对象的锁定,永远不会再次获取。所以你最好的镜头就像“无尽的饥饿”。

我个人称之为“生产者 - 消费者错误”,因为等待通知机制描述和管理“生产者 - 消费者”模式的线程协调,这种方法(没有通知的等待)只是开发人员错误或错过了方法。

答案 2 :(得分:1)

死锁基本上意味着一个线程正在持有一个锁(第一个锁)然后想要获取它永远无法获取的另一个锁(第二个锁),因为第二个锁由另一个想要获取第一个锁的线程持有。这也可能发生在一个线程链中,例如,其中Thread1具有LockA,Thread2具有LockB,Thread3具有LockC并且它们正在等待其他线程持有的锁(例如,Thread1想要LockB,Thread2想要LockC和Thread3)对于LockA)。在这种情况下,没有任何线程可以继续。

只有这种情况称为死锁;持有锁的线程等待另一个锁,除非它释放锁,否则永远不会被获取。

锁对象上的这个调用wait在技术上释放了线程所持有的锁。

所以,为了回答你的问题,我认为你不能把你在问题中提到的场景称为死锁。

答案 3 :(得分:0)

对于这种情况,最好使用wait(timeout)方法而不是wait()。 即使没有来自其他线程的通知,它也会在超时期限后释放锁定

答案 4 :(得分:0)

@ S.Doe随着你的问题的编辑,情况是死锁,因为有2个线程,两个都处于等待队列,并且没有执行任何代码。 没有第三个线程也意味着没有线程处于runnable / running状态。这是一个僵局。

答案 5 :(得分:0)

您无法使用此代码控制哪个线程将首先获取锁定。如果消费者首先获得,它对notify()的调用不会唤醒任何线程,而是等待离开锁定。现在生产者获得锁定,生产并等待。 这是一般竞争条件问题,您需要先控制哪个线程应该开始执行(本场景中为Producer) 您可以使用CyclicBarrier来控制线程调用的顺序。

答案 6 :(得分:0)

我认为Krasimir和Andy都给出了正确的答案。我想提供一个额外的证据(某种程度上),这个情况在技术上不是死锁。

这是因为这个程序上的Java线程转储没有报告这是一个死锁。尽管Java线程转储可能是错误报告,但我们可以假设如果存在死锁,则线程转储可以很好地报告它。

所以,我在程序的文字顺序中listener (consumer) 之前启动changer (producer)来运行您的程序,就像您一样,我收到了挂起。在这一点上,我得到了一个线程转储(有各种方法可以做到这一点,看看你的IDE是最简单的)。线程转储的相关部分在这里[1]。

如果这是一个死锁,那么线程转储就会明确地说(类似发现一个Java级别死锁)。但既然没有,我们可以放心,这不是僵局。

查看线程转储(这是每个线程的'堆栈'转储)仍然非常具有指导性。您可以看到生产者和消费者线程首先按照<0x000000076abca250>行的建议锁定了锁lock ...(进入监视器)。奇怪的是(或者没有),他们两个都在等待同一个锁(如waiting on ...)线所示!

这是进入thread's wait set的微妙之处。为了让一个线程等待一个锁,它必须首先放弃相同的锁。

这正是consumer#wait所做的(下面转储中的第二个线程):放弃了独占锁,所以其他一些线程实际上可以抓住它并希望通过notify唤醒他。

好吧,consumer现在已被争夺该锁定。请注意,在notify之前consumer已经完成的wait来电可能会或可能不会误入歧途。如果确实如此,那么我们就有这种悬而未决的情况,我们正试图推理。

同时,生产者线程也开始运行(可能稍后)。由于消费者已“放弃”锁定以等待它,因此生产者抓住锁定,现在它也执行wait。就像消费者一样,现在它正在等待在同一个锁上,同样希望有人会来并唤醒他!

同样,从技术上讲,两个线程都没有专门锁定任何锁。由于两个线程都在等待,它们已经放弃了锁定并且应该有其他线程出现(太糟糕了,不是在这种情况下!),他们的希望可能会实现。但在这种情况下,该程序将永远挂起,因为没有其他任何东西可以改变这种情况(JVM挂起有其原因在线程是非守护进程,但这是另一个细节)。因此,wait-notify是Java中用于线程通信的低级基元,它们的适当用途是通过所谓的条件变量。这就像线程承认他们正在处理共享资源,并且他们通过这些原语相互通信任何边界情况(例如,共享队列是空的)。

如果一个线程实际上持有一个锁L1(即它在synchronized部分内执行)并且突然,它需要抓住另一个锁L2来执行它的任务,同时又需要另一个线程L2,正在寻找抢夺L1,然后发生死锁。由于这不是一种情况,这不是一个僵局。

话虽如此,我还是有很多建议来改进这段代码。就像约书亚布洛赫所说的那样,“示例代码必须是典范”:

  1. Object lock = new Object();类中删除行PC。它没有任何意义。你没有锁定它。您正在锁定与PC实例关联的监视器:PC pc = new PC();
  2. 考虑将SAMPLE_INT重命名为count 中的public static int SAMPLE_INT = 0;将其设为volatile。无法保证对此变量所做的更改可见到另一个线程。
  3. 无需在SAMPLE_INT中定义ProducerConsumer,在PC中定义。
  4. 与某人建议一样,请考虑在启动主题时使用CountdownLatch
  5. [1]线程转储(我使用了tmp包名称):

    2016-02-23 15:02:49
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.74-b02 mixed mode):
    
    "DestroyJavaVM" #13 prio=5 os_prio=31 tid=0x00007f80b381c800 nid=0x1303 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Thread-0" #11 prio=5 os_prio=31 tid=0x00007f80b480b000 nid=0x5903 in Object.wait() [0x00000001294e5000]
       java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076abca250> (a tmp.PC)
        at java.lang.Object.wait(Object.java:502)
        at tmp.PC.producer(ProducerConsumer.java:44)
        - locked <0x000000076abca250> (a tmp.PC)
        at tmp.ProducerConsumer$1.run(ProducerConsumer.java:14)
        at java.lang.Thread.run(Thread.java:745)
    
    "Thread-1" #12 prio=5 os_prio=31 tid=0x00007f80b6801000 nid=0x5703 in Object.wait() [0x00000001293e2000]
       java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076abca250> (a tmp.PC)
        at java.lang.Object.wait(Object.java:502)
        at tmp.PC.consumer(ProducerConsumer.java:55)
        - locked <0x000000076abca250> (a tmp.PC)
        at tmp.ProducerConsumer$2.run(ProducerConsumer.java:24)
        at java.lang.Thread.run(Thread.java:745)