Java单线程程序中的死锁问题

5

我读到过单线程Java程序可能会发生死锁的情况。我很好奇是怎么回事,毕竟没有竞争。据我所记,书籍中都是用多个线程来举例说明的。如果可以,请给出一个单线程死锁的例子。

7个回答

6

关键是你如何定义“死锁”。

例如,以下情况相当现实:单线程应用程序使用大小限制的队列,当达到限制时会阻塞。只要没有达到限制,单线程就可以正常工作。但是当达到限制时,线程将永远等待(不存在的)其他线程从队列中取出某些内容,以便它可以继续执行。


3
我认为这不是死锁。否则,while(true){}就是一个死锁。 - Bozho
@Bozho 这是一个代码块,不是死锁,但 while (true) 与此无关。 - user207421

2
在多核处理器变得便宜之前,所有桌面电脑都只有单核处理器。单核处理器只能运行一个线程。那么多线程是如何工作的呢?针对Java语言最简单的实现方式如下:
线程1的代码:
 doSomething();
 yield();    // may switch to another thread
 doSomethingElse();

thread2的代码:

 doSomething2();
 yield();    // may switch to another thread
 doSomethingElse2();

这被称为协作式多线程 - 所有的工作都由一个线程完成,因此在Windows 3.1中进行了多线程处理。

今天的多线程称为抢占式多线程,它只是对协作式多线程的轻微修改,其中yield()会自动定期调用。

所有这些可能归结为以下交错:

doSomething();
doSomething2();
doSomethingElse2();
doSomethingElse();

或者:
doSomething();
doSomething2();
doSomethingElse();
doSomethingElse2();

我们将多线程代码转换为单线程代码。因此,如果在多线程程序中可能出现死锁,在单线程程序中也是可能的。例如:
线程1:
queue.put(x);
yield();

线程2:

x = queue.waitAndGet()
yield();

这个交错方式是可以的:

queue.put(x);
x = queue.waitAndGet()

但是在这里我们遇到了死锁:

x = queue.waitAndGet()
queue.put(x);

是的,单线程程序也可能出现死锁。



每次都会发生死锁吗? - Frank
有了这两个“线程”和yield(),它将随机发生。在现代操作系统中(使用预先抢占式线程而不是yield()),它可以运行;但是在第二个单线程解决方案中,每次都会发生死锁,请用BlockingQueue中的适当方法替换我的“queue.xxxx()”方法。线程将认为它正在等待其他线程,但实际上它正在等待自己。 :-) - iirekm
使用yield()在单线程环境中可以进行多线程处理的事实并不影响问题域。问题不是关于多线程情况的,而是关于单线程情况的。 - pbr

1

我敢说是的

如果您在同一线程中连续尝试获取相同的锁,则取决于锁或锁定实现的类型,它是否检查该锁是否由同一线程获取。如果实现不检查此项,则会出现死锁。

对于synchronized,这是被检查的,但我找不到Semaphore的保证。

如果您使用其他类型的锁,则必须检查规范以了解其保证的行为!

另外,正如已经指出的那样,通过读取/写入受限缓冲区,您可能会被阻塞(这与死锁不同)。例如,您将内容写入分槽缓冲区,并仅在某些条件下从中读取。当您无法再插入时,您将等待直到有一个空闲的插槽,但由于您自己进行了读取,因此这种情况永远不会发生。

所以我敢说答案应该是肯定的,尽管不那么容易并且通常更容易检测。

希望能帮到您

Mario


1
即使您的Java代码是单线程的,仍然存在信号处理程序,这些处理程序在与主线程不同的线程/上下文中执行。
因此,在Linux系统上运行Java时,即使使用单线程解决方案,死锁也可能发生。

QED. -pbr


+1 没有考虑到信号! - Mario The Spoon

0

不。

死锁是多个线程(或进程)试图以无法继续的方式获取锁而导致的结果。

考虑一下维基百科文章中的一句话:(http://en.wikipedia.org/wiki/Deadlock)

“当两列火车在交叉口相遇时,两者都必须完全停止,直到另一个离开为止。”


多个线程或进程... - NVRAM
好观点Carter。我已编辑了我的评论,谢谢。 - philipk
是的,多个线程(即不止0个)就是我们所讨论的。 - Ingo

0

不,对我来说听起来相当不可能。

但是你可以在另一个应用程序锁定你要请求的资源时理论上锁定系统资源,而该应用程序将请求你已经锁定的资源。 死锁。

但操作系统应该能够通过检测并将两个资源同时分配给一个应用程序来解决这个问题。这种情况发生的机会微乎其微,但任何好的操作系统都应该能够处理这一千万分之一的机会。

如果您仔细设计并一次只锁定一个资源,则不会发生这种情况。


为什么要踩这个回答?它准确地讨论了一件在十亿分之一的情况下可能发生的事情。 - Frank
我没有给你点踩,但我差点就这么做了。你的第二段是正确的,因此如果应用程序的设计方式符合你所描述的方式,发生这种情况的可能性非常高。所以你的第三段对于大多数操作系统上的大多数资源来说是错误的。毕竟,操作系统到底会采取什么措施来避免死锁呢? - NVRAM
你的第三段假设该应用程序只需要一种资源就能够完成其目标,但对于许多问题来说,这根本行不通。 - NVRAM
操作系统可以进行资源调度,但对于程序员来说这是非常困难的。至少在我7年前做业余内核时是很难的。人们必须考虑许多情况,最坏的情况是两个进程都对它们获得锁的资源做了某些操作 - 如果是这样,就不能只是将其中一个资源交给另一个进程并让其继续执行。我自己7年前的解决方案很简单粗暴 - 它只是翻转硬币并希望一切顺利。 - Frank

-1

这其实很简单:

BlockingQueue bq = new ArrayBlockingQueue(1);
bq.take();

会死锁。


这不是死锁,而是阻塞。 - user207421
@EJP 挑剔。贴这个帖子的线程已经死了。太容易了。 - Ingo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接