编写一个肯定会陷入僵局的程序

use*_*434 83 java concurrency deadlock

我最近在接受采访时提到了这个问题.

我回答说如果交错出错就会发生死锁,但是访问者坚持认为可以编写一个总是会陷入死锁的程序而不管交错.

我们可以写这样的程序吗?你能指点我这样的示例程序吗?

Eri*_*ert 96

更新:这个问题是我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() 
  { }
}
Run Code Online (Sandbox Code Playgroud)

  • @Lieven:静态构造函数必须运行不超过*一次*并且它必须在*首次调用类中的任何静态方法之前运行*.Main是一个静态方法,所以主线程调用静态ctor.为了确保它只运行一次,CLR取出一个锁,在静态ctor完成之前不会释放.当ctor启动一个新线程时,该线程也会调用静态方法,因此CLR会尝试锁定以查看是否需要运行ctor.同时主线程"加入"被阻塞的线程,现在我们遇到了死锁. (41认同)
  • @artbristol:我从来没有写过一行Java代码; 我认为现在没有理由开始. (32认同)
  • @Voo:你的记忆力很好.Neal Gafter--"Java Puzzlers"的合着者 - 几年前我在奥斯陆开发者大会的"C#Puzzlers"演讲中提出了一个相当混淆的代码版本. (11认同)
  • 我对理论C#的了解是有限的,但我认为类加载器保证代码是像Java一样运行单线程的.我很确定Java Puzzlers中有类似的例子. (4认同)
  • 和java实现? (4认同)
  • 哦,我以为你对你的练习#2有了答案.恭喜获得这么多赞成回答Java问题的赞成. (3认同)
  • @EricLippert我试图准确理解这里发生了什么.当你说主线程"加入"被阻塞的线程时,我看不出它是如何加入的.是不是主线程卡在`thread.Join();`它从来没有真正加入?我看到它的方式是调用`MyClass`静态ctor,它接受一个锁,然后`Initialize`尝试再次调用ctor,但由于锁定而不能.所以主线程将永远被`thread.Join();`所困 (3认同)

art*_*tol 27

这里的锁存器确保当每个线程试图锁定另一个时,两个锁都被保持:

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();

   }

}
Run Code Online (Sandbox Code Playgroud)

有意运行jconsole,它会在Threads选项卡中正确显示死锁.

  • 到目前为止这是最好的,但是我用一个合适的闩锁代替`sleep`:理论上,我们在这里有一个竞争条件.虽然我们几乎可以肯定0.5秒就足够了,但这对面试任务来说并不算太好. (3认同)

Gre*_*tes 24

当线程(或任何平台调用其执行单元)获取资源时,就会发生死锁,其中每个资源一次只能由一个线程持有,并以这样的方式保留这些资源,使得无法抢占保留,以及线程之间存在一些"循环"关系,使得死锁中的每个线程都在等待获取另一个线程持有的某些资源.

因此,避免死锁的一种简单方法是对资源进行一些总排序,并强制规定资源只能由线程按顺序获取.相反,可以通过运行获取资源的线程有意地创建死锁,但不要按顺序获取它们.例如:

两个线程,两个锁.第一个线程运行一个循环,尝试以某种顺序获取锁,第二个线程运行一个循环,尝试以相反的顺序获取锁.成功获取锁后,每个线程都释放两个锁.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,在这个问题中有一些评论指出了死锁的可能性确定性之间的区别.从某种意义上说,这种区别是一个学术问题.从实际的角度来看,我当然希望看到一个正在运行的系统,它与我上面编写的代码没有死锁:)

然而,面试问题有时可能是学术性的,而这个问题确实在标题中有"肯定"这个词,所以接下来的是一个肯定会陷入僵局的程序.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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 很抱歉引用Linus,"谈话很便宜.给我看一下代码." - 这是一个很好的任务,而且比它看起来要难得多. (3认同)
  • 可以在没有死锁的情况下运行此代码 (2认同)

Yur*_*rev 14

以下是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();      
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 我认为使用run in run方法有点误导.它建议除了静态块中的那个之外的其他连接需要获得死锁,而死锁是由"new Lock()"语句引起的.我的重写,使用像C#示例中的静态方法:http://stackoverflow.com/a/16203272/2098232 (4认同)

Clo*_*ble 11

以下是文档中的示例:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • "它极有可能"不足以"肯定会陷入僵局" (4认同)
  • @bezz`sleep`很无聊.虽然我确实认为没有线程会在5秒内启动,但无论如何都是竞争条件.你不想聘请一个程序员来依靠'sleep()`来解决竞争条件:) (4认同)
  • +1用于链接java教程. (2认同)
  • @ user268396好吧,根本问题是微不足道和无聊的:)任务的重点是找出 - 或证明你理解 - 令人惊讶的是难以获得保证死锁(以及在异步世界中保证任何东西) ). (2认同)

luk*_*657 8

我重写了Yuriy Zubarev的Java版本的Eric Lippert发布​​的死锁示例: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");
    }

}
Run Code Online (Sandbox Code Playgroud)


alf*_*alf 5

这不是一个你可以得到的最简单的面试任务:在我的项目中,它使一个团队的工作陷入瘫痪一整天.让程序停止很容易,但是很难让它进入线程转储写入的状态,

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)
Run Code Online (Sandbox Code Playgroud)

因此,目标是获得JVM认为会陷入僵局的死锁.显然,没有解决方案

synchronized (this) {
    wait();
}
Run Code Online (Sandbox Code Playgroud)

从那个意义上来说,即使他们确实会永远停止.依靠竞争条件也不是一个好主意,因为在面试中你通常想要展示一些可以证明有效的东西,而不是大部分时间应该起作用的东西.

现在,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) {
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我记得,synchronized只有解决方案适合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) {
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意我们用对象监视器替换一个锁存器("a"用作对象).