我最近在一次采访中提出了这个问题。
我回答说,如果交织出错,就会发生死锁,但是访调员坚持认为,可以编写一个无论交织而总是陷入死锁的程序。
我们可以编写这样的程序吗?您能指出我这样的示例程序吗?
我最近在一次采访中提出了这个问题。
我回答说,如果交织出错,就会发生死锁,但是访调员坚持认为,可以编写一个无论交织而总是陷入死锁的程序。
我们可以编写这样的程序吗?您能指出我这样的示例程序吗?
Answers:
更新:这个问题是我2013年1月博客的主题。感谢您提出的好问题!
无论线程如何调度,我们如何编写一个始终会陷入死锁的程序?
这是C#中的示例。请注意,该程序似乎不包含锁和共享数据。它只有一个局部变量和三个语句,但死锁具有100%的确定性。一个人很难想出一个更简单的程序来确定性地陷入僵局。
练习#1:解释这种僵局。(答案在注释中。)
对读者#2的练习:演示Java中相同的死锁。(答案是在这里:https : //stackoverflow.com/a/9286697/88656)
class MyClass
{
static MyClass()
{
// Let's run the initialization on another thread!
var thread = new System.Threading.Thread(Initialize);
thread.Start();
thread.Join();
}
static void Initialize()
{ /* TODO: Add initialization code */ }
static void Main()
{ }
}
此处的闩锁可确保当每个线程尝试锁定另一个时,两个锁定均被保持:
import java.util.concurrent.CountDownLatch;
public class Locker extends Thread {
private final CountDownLatch latch;
private final Object obj1;
private final Object obj2;
Locker(Object obj1, Object obj2, CountDownLatch latch) {
this.obj1 = obj1;
this.obj2 = obj2;
this.latch = latch;
}
@Override
public void run() {
synchronized (obj1) {
latch.countDown();
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException();
}
synchronized (obj2) {
System.out.println("Thread finished");
}
}
}
public static void main(String[] args) {
final Object obj1 = new Object();
final Object obj2 = new Object();
final CountDownLatch latch = new CountDownLatch(2);
new Locker(obj1, obj2, latch).start();
new Locker(obj2, obj1, latch).start();
}
}
运行jconsole很有意思,它将在Threads选项卡中正确显示死锁。
sleep
为适当的闩锁:理论上,我们这里有一个竞争条件。虽然我们几乎可以肯定0.5秒就足够了,但对于面试任务来说并不太好。
死锁发生在线程(或平台所调用的执行单元)获取资源时,其中每个资源一次只能由一个线程持有,并以不能抢占的方式持有这些资源,并且线程之间存在某种“循环”关系,因此死锁中的每个线程都在等待获取另一线程持有的某些资源。
因此,避免死锁的一种简单方法是对资源进行总体排序,并强加一个规则,即只有线程才能按顺序获取资源。相反,可以通过运行获取资源但不按顺序获取资源的线程来故意创建死锁。例如:
两个线程,两个锁。第一个线程运行一个尝试以某种顺序获取锁的循环,第二个线程运行一个尝试以相反顺序获取锁的循环。成功获取锁后,每个线程都释放两个锁。
public class HighlyLikelyDeadlock {
static class Locker implements Runnable {
private Object first, second;
Locker(Object first, Object second) {
this.first = first;
this.second = second;
}
@Override
public void run() {
while (true) {
synchronized (first) {
synchronized (second) {
System.out.println(Thread.currentThread().getName());
}
}
}
}
}
public static void main(final String... args) {
Object lock1 = new Object(), lock2 = new Object();
new Thread(new Locker(lock1, lock2), "Thread 1").start();
new Thread(new Locker(lock2, lock1), "Thread 2").start();
}
}
现在,在这个问题上有一些评论指出了死锁可能性和确定性之间的区别。从某种意义上说,区别是一个学术问题。从实际的角度来看,我当然希望看到一个运行中的系统不会死于我上面编写的代码:)
但是,面试问题有时可能是学术性问题,因此该SO问题的标题中确实有“肯定”一词,因此,随之而来的是一个肯定会陷入僵局的程序。Locker
创建两个对象,每个对象都有两个锁,一个CountDownLatch
用于在线程之间进行同步。每个Locker
锁先锁定第一个锁,然后将锁存器递减一次。当两个线程都已获取锁并递减计数时,它们会越过闩锁屏障并尝试获取第二个锁,但是在每种情况下,另一个线程已经拥有了所需的锁。这种情况导致一定的僵局。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CertainDeadlock {
static class Locker implements Runnable {
private CountDownLatch latch;
private Lock first, second;
Locker(CountDownLatch latch, Lock first, Lock second) {
this.latch = latch;
this.first = first;
this.second = second;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
try {
first.lock();
latch.countDown();
System.out.println(threadName + ": locked first lock");
latch.await();
System.out.println(threadName + ": attempting to lock second lock");
second.lock();
System.out.println(threadName + ": never reached");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(final String... args) {
CountDownLatch latch = new CountDownLatch(2);
Lock lock1 = new ReentrantLock(), lock2 = new ReentrantLock();
new Thread(new Locker(latch, lock1, lock2), "Thread 1").start();
new Thread(new Locker(latch, lock2, lock1), "Thread 2").start();
}
}
这是一个遵循Eric Lippert的Java示例:
public class Lock implements Runnable {
static {
System.out.println("Getting ready to greet the world");
try {
Thread t = new Thread(new Lock());
t.start();
t.join();
} catch (InterruptedException ex) {
System.out.println("won't see me");
}
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
public void run() {
Lock lock = new Lock();
}
}
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s"
+ " has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s"
+ " has bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse =
new Friend("Alphonse");
final Friend gaston =
new Friend("Gaston");
new Thread(new Runnable() {
public void run() { alphonse.bow(gaston); }
}).start();
new Thread(new Runnable() {
public void run() { gaston.bow(alphonse); }
}).start();
}
}
Object invokeAndWait(Callable task)
方法。然后,所有Callable t1
需要做的就是invokeAndWait()
为Callable t2
在返回前,它的续航时间,反之亦然。
sleep
很无聊。虽然我确实相信5秒钟内不会有任何线程启动,但是无论如何,这都是竞争条件。您不想雇用将依赖sleep()
于解决竞赛条件的程序员:)
我已经重写了埃里克·利珀特(Eric Lippert)发布的Yuriy Zubarev的Java版本的死锁示例:https://stackoverflow.com/a/9286697/2098232,使其与C#版本更加相似。如果Java的初始化块的工作方式类似于C#静态构造函数,并且首先获取了锁,我们不需要另一个线程来调用join方法来获取死锁,则它只需要调用Lock类中的某些静态方法,例如原始的C#例。由此产生的死锁似乎可以证实这一点。
public class Lock {
static {
System.out.println("Getting ready to greet the world");
try {
Thread t = new Thread(new Runnable(){
@Override
public void run() {
Lock.initialize();
}
});
t.start();
t.join();
} catch (InterruptedException ex) {
System.out.println("won't see me");
}
}
public static void main(String[] args) {
System.out.println("Hello World!");
}
public static void initialize(){
System.out.println("Initializing");
}
}
这不是您可以完成的最简单的面试任务:在我的项目中,它使团队的工作瘫痪了一整天。使程序停止很容易,但是要使其处于线程转储写类似的状态非常困难,
Found one Java-level deadlock:
=============================
"Thread-2":
waiting to lock monitor 7f91c5802b58 (object 7fb291380, a java.lang.String),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 7f91c6075308 (object 7fb2914a0, a java.lang.String),
which is held by "Thread-2"
Java stack information for the threads listed above:
===================================================
"Thread-2":
at uk.ac.ebi.Deadlock.run(Deadlock.java:54)
- waiting to lock <7fb291380> (a java.lang.String)
- locked <7fb2914a0> (a java.lang.String)
- locked <7f32a0760> (a uk.ac.ebi.Deadlock)
at java.lang.Thread.run(Thread.java:680)
"Thread-1":
at uk.ac.ebi.Deadlock.run(Deadlock.java:54)
- waiting to lock <7fb2914a0> (a java.lang.String)
- locked <7fb291380> (a java.lang.String)
- locked <7f32a0580> (a uk.ac.ebi.Deadlock)
at java.lang.Thread.run(Thread.java:680)
因此,目标是获得一个死锁,JVM将其视为死锁。显然,没有像
synchronized (this) {
wait();
}
即使它们确实会永远停止,也可以在这种意义上发挥作用。同样,依靠种族条件也不是一个好主意,因为在面试过程中,您通常想展示一些可以证明是可行的东西,而不是大多数情况下应该有用的东西。
现在,从sleep()
某种意义上说,解决方案是可以的,很难想象这是行不通的,但是不公平的情况(我们处于公平的运动中,不是吗?)。@artbristol的解决方案(我的是相同的,只是与监视器不同的对象)很好,但是很长,并且使用新的并发原语来使线程处于正确的状态,这并不是那么有趣:
public class Deadlock implements Runnable {
private final Object a;
private final Object b;
private final static CountDownLatch latch = new CountDownLatch(2);
public Deadlock(Object a, Object b) {
this.a = a;
this.b = b;
}
public synchronized static void main(String[] args) throws InterruptedException {
new Thread(new Deadlock("a", "b")).start();
new Thread(new Deadlock("b", "a")).start();
}
@Override
public void run() {
synchronized (a) {
latch.countDown();
try {
latch.await();
} catch (InterruptedException ignored) {
}
synchronized (b) {
}
}
}
}
我确实记得synchronized
-only解决方案适合11..13行代码(不包括注释和导入),但是还没有回想起实际的窍门。如果我会更新。
更新:这是关于的丑陋解决方案synchronized
:
public class Deadlock implements Runnable {
public synchronized static void main(String[] args) throws InterruptedException {
synchronized ("a") {
new Thread(new Deadlock()).start();
"a".wait();
}
synchronized ("") {
}
}
@Override
public void run() {
synchronized ("") {
synchronized ("a") {
"a".notifyAll();
}
synchronized (Deadlock.class) {
}
}
}
}
请注意,我们将闩锁替换为对象监视器("a"
用作对象)。
LOCKED
和waiting to lock
是潜移默化的,不是你早餐时读到的东西。但是,你可能是对的。让我改一下。
import java.util.concurrent.CountDownLatch;
public class SO8880286 {
public static class BadRunnable implements Runnable {
private CountDownLatch latch;
public BadRunnable(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
System.out.println("Thread " + Thread.currentThread().getId() + " starting");
synchronized (BadRunnable.class) {
System.out.println("Thread " + Thread.currentThread().getId() + " acquired the monitor on BadRunnable.class");
latch.countDown();
while (true) {
try {
latch.await();
} catch (InterruptedException ex) {
continue;
}
break;
}
}
System.out.println("Thread " + Thread.currentThread().getId() + " released the monitor on BadRunnable.class");
System.out.println("Thread " + Thread.currentThread().getId() + " ending");
}
}
public static void main(String[] args) {
Thread[] threads = new Thread[2];
CountDownLatch latch = new CountDownLatch(threads.length);
for (int i = 0; i < threads.length; ++i) {
threads[i] = new Thread(new BadRunnable(latch));
threads[i].start();
}
}
}
程序总是死锁,因为每个线程都在屏障旁等待其他线程,但是为了等待屏障,线程必须将监视器保持打开状态BadRunnable.class
。
} catch (InterruptedException ex) { continue; }
…美丽
这里有一个Java示例
http://baddotrobot.com/blog/2009/12/24/deadlock/
绑架者在拒绝交出受害者直到获得现金之前陷入僵局,但谈判代表拒绝交出现金直到获得受害者为止。
一个简单的搜索为我提供了以下代码:
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s"
+ " has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s"
+ " has bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse =
new Friend("Alphonse");
final Friend gaston =
new Friend("Gaston");
new Thread(new Runnable() {
public void run() { alphonse.bow(gaston); }
}).start();
new Thread(new Runnable() {
public void run() { gaston.bow(alphonse); }
}).start();
}
}
资料来源:死锁
这是一个示例,其中一个持有锁的线程启动了另一个想要相同锁的线程,然后启动器一直等到启动完成...永远:
class OuterTask implements Runnable {
private final Object lock;
public OuterTask(Object lock) {
this.lock = lock;
}
public void run() {
System.out.println("Outer launched");
System.out.println("Obtaining lock");
synchronized (lock) {
Thread inner = new Thread(new InnerTask(lock), "inner");
inner.start();
try {
inner.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class InnerTask implements Runnable {
private final Object lock;
public InnerTask(Object lock) {
this.lock = lock;
}
public void run() {
System.out.println("Inner launched");
System.out.println("Obtaining lock");
synchronized (lock) {
System.out.println("Obtained");
}
}
}
class Sample {
public static void main(String[] args) throws InterruptedException {
final Object outerLock = new Object();
OuterTask outerTask = new OuterTask(outerLock);
Thread outer = new Thread(outerTask, "outer");
outer.start();
outer.join();
}
}
这是一个例子:
两个线程正在运行,每个线程都在等待对方释放锁
公共类ThreadClass扩展了线程{
String obj1,obj2;
ThreadClass(String obj1,String obj2){
this.obj1=obj1;
this.obj2=obj2;
start();
}
public void run(){
synchronized (obj1) {
System.out.println("lock on "+obj1+" acquired");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waiting for "+obj2);
synchronized (obj2) {
System.out.println("lock on"+ obj2+" acquired");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行此命令将导致死锁:
公共类SureDeadlock {
public static void main(String[] args) {
String obj1= new String("obj1");
String obj2= new String("obj2");
new ThreadClass(obj1,obj2);
new ThreadClass(obj2,obj1);
}
}