私は最近、この質問を面接で尋ねました。
インターリーブがうまくいかないとデッドロックが発生すると答えましたが、インタビュアーはインターリーブに関係なく常にデッドロックになるプログラムを書くことができると主張しました。
そのようなプログラムを書くことはできますか?そのようなサンプルプログラムを教えていただけますか?
私は最近、この質問を面接で尋ねました。
インターリーブがうまくいかないとデッドロックが発生すると答えましたが、インタビュアーはインターリーブに関係なく常にデッドロックになるプログラムを書くことができると主張しました。
そのようなプログラムを書くことはできますか?そのようなサンプルプログラムを教えていただけますか?
回答:
更新:この質問は、2013年1月の私のブログの主題でした。素晴らしい質問をありがとう!
スレッドがどのようにスケジュールされていても、常にデッドロックになるプログラムを作成するにはどうすればよいですか?
これがC#の例です。プログラムにはロックや共有データが含まれていないように見えることに注意してください。ローカル変数が1つとステートメントが3つしかないにもかかわらず、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を実行するのは興味深いことです。これにより、[スレッド]タブにデッドロックが正しく表示されます。
sleep
、適切なラッチに置き換えます。理論的には、ここに競合状態があります。0.5秒で十分だとほぼ確信できますが、面接タスクにはあまり適していません。
デッドロックは、スレッド(またはプラットフォームが実行ユニットと呼ぶもの)がリソースを取得するときに発生します。各リソースは一度に1つのスレッドのみが保持でき、保持をプリエンプトできないようにそれらのリソースを保持します。デッドロック内の各スレッドが別のスレッドによって保持されているリソースの取得を待機するように、スレッド間には「循環」関係が存在します。
したがって、デッドロックを回避する簡単な方法は、リソースに全体的な順序を付け、リソースはスレッドによってのみ順番に取得されるというルールを課すことです。逆に、デッドロックは、リソースを取得するスレッドを実行することによって意図的に作成できますが、リソースを順番に取得しません。例えば:
2つのスレッド、2つのロック。最初のスレッドは特定の順序でロックを取得しようとするループを実行し、2番目のスレッドは反対の順序でロックを取得しようとするループを実行します。各スレッドは、ロックを正常に取得した後、両方のロックを解放します。
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の質問のタイトルには「確かに」という単語が含まれているため、以下は確実に行き詰まるプログラムです。2つのLocker
オブジェクトが作成され、それぞれに2つのロックが与えられCountDownLatch
、スレッド間の同期に使用されます。それぞれLocker
が最初のロックをロックしてから、ラッチを1回カウントダウンします。両方のスレッドがロックを取得してラッチをカウントダウンすると、ラッチバリアを通過して、2番目のロックを取得しようとしますが、いずれの場合も、もう一方のスレッドはすでに目的のロックを保持しています。この状況では、特定のデッドロックが発生します。
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();
}
}
これは、EricLippertの例に従った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()
競合状態の解決に依存するプログラマーを雇いたくない:)
EricLippertによって投稿されたデッドロックの例のYuriyZubarevのJavaバージョンを書き直しました:https://stackoverflow.com/a/9286697/2098232 C#バージョンにより近くなります。Javaの初期化ブロックがC#静的コンストラクターと同様に機能し、最初にロックを取得する場合、デッドロックを取得するためにjoinメソッドを呼び出すために別のスレッドは必要ありません。元のC#のように、Lockクラスから静的メソッドを呼び出すだけで済みます。例。結果として生じるデッドロックはこれを確認しているようです。
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
のみのソリューションは、(コメントや輸入を除く)コードの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
、あなたが朝食時に読んで何か微妙ではありません。しかし、まあ、あなたはおそらく正しいです。言い換えさせてください。
このC#バージョンでは、Javaはかなり似ているはずです。
static void Main(string[] args)
{
var mainThread = Thread.CurrentThread;
mainThread.Join();
Console.WriteLine("Press Any key");
Console.ReadKey();
}
console
ステートメントを削除した場合にデッドロックを作成するための本当に最短のC#プログラム。Main
関数全体をThread.CurrentThread.Join();
。と書くだけです。
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();
}
}
出典:デッドロック
これは、ロックを保持している1つのスレッドが同じロックを必要とする別のスレッドを開始し、スターターが開始が終了するまで待機するサンプルです...永久に:
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();
}
}
次に例を示します。
2つのスレッドが実行されており、それぞれが他のスレッドがロックを解放するのを待っています
パブリッククラスThreadClassはThread {を拡張します
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);
}
}