Kesinlikle çıkmaza girecek bir program yazın [kapalı]


86

Geçenlerde bir röportajda sorulan bu soruları aldım.

Serpiştirme yanlış giderse kilitlenme oluştuğunu söyledim, ancak görüşmeci, serpiştirmeden bağımsız olarak her zaman kilitlenecek bir programın yazılabileceği konusunda ısrar etti.

Böyle bir program yazabilir miyiz? Beni bunun gibi bir örnek programa yönlendirebilir misin?


3
Görüşmeci kesinlikle aptal bir adamdır.
Aslan

23
Görüşmeyi yapan kişi kesinlikle aptal bir adam değil. Bir konunun tam olarak anlaşılması, kutup uç durumlarını açıklayabilmeniz gerektiği anlamına gelir: programı asla kilitlenmeyecek ve her zaman kilitlenecek hale getirmek.
Yuriy Zubarev

Yanıtlar:


100

GÜNCELLEME: Bu soru, Ocak 2013'teki blogumun konusuydu . Bu mükemmel soru için teşekkürler!


İş parçacıkları nasıl programlanırsa planlansın her zaman kilitlenecek bir programı nasıl yazabiliriz?

İşte C # bir örnek. Programın kilit ve paylaşılan veri içermediğini unutmayın . Yalnızca tek bir yerel değişkeni ve üç ifadesi vardır ve yine de% 100 kesinlik ile kilitlenir. Kesinlikle çıkmaza giren daha basit bir program bulmak zor olurdu.

Okuyucu için alıştırma # 1: Bu çıkmazın nasıl kilitlendiğini açıklayın. (Yorumlarda bir cevap var.)

Okuyucu # 2 için alıştırma: Java'daki aynı kilitlenmeyi gösterin. (Bir cevap burada: 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() 
  { }
}

4
Teorik C # bilgim sınırlıdır, ancak sınıf yükleyicinin kodun Java'da olduğu gibi tek iş parçacıklı çalıştırılacağını garanti ettiğini varsayıyorum. Java Bulmacaları'nda da benzer bir örnek olduğundan oldukça eminim.
Voo

11
@Voo: İyi bir hafızan var. "Java Bulmacaları" nın ortak yazarı Neal Gafter ve ben birkaç yıl önce Oslo Geliştirici Konferansı'ndaki "C # Bulmacaları" konuşmamızda bu kodun daha karmaşık bir versiyonunu sundum.
Eric Lippert

41
@Lieven: Statik yapıcı fazla çalıştırmalısınız kez ve çalıştırmalısınız önce sınıfta statik yönteme ilk çağrı. Main statik bir yöntemdir, bu nedenle ana iş parçacığı statik ctor'u çağırır. Yalnızca bir kez çalışmasını sağlamak için CLR, statik ctor bitene kadar serbest bırakılmayan bir kilidi çıkarır. Ctor yeni bir evre başlattığında, bu evre aynı zamanda statik bir yöntemi çağırır, bu nedenle CLR, ctor'u çalıştırması gerekip gerekmediğini görmek için kilidi almaya çalışır. Bu arada ana iş parçacığı engellenen ileti dizisine "katılıyor" ve şimdi kilitlenmemiz var.
Eric Lippert

33
@artbristol: Bir satır Java kodu yazmadım; Şimdi başlamak için bir sebep göremiyorum.
Eric Lippert

4
Oh, 2. Egzersizinize bir cevabınız olduğunu varsaydım. Bir Java sorusunu yanıtladığınız için bu kadar olumlu oy aldığınız için tebrikler.
artbristol

27

Buradaki mandal, her iplik diğerini kilitlemeye çalıştığında her iki kilidin de tutulmasını sağlar:

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

   }

}

İplikler sekmesindeki kilitlenmeyi doğru bir şekilde gösterecek olan jconsole'u çalıştırmak ilginç.


3
Şimdiye kadarki en iyisi bu, ancak yerine sleepuygun bir mandal koyardım: teorik olarak, burada bir yarış durumumuz var. 0,5 saniyenin yeterli olduğundan neredeyse emin olsak da, bir röportaj görevi için çok iyi değil.
Alf

25

Kilitlenme , iş parçacıkları (veya platformunuz yürütme birimlerini ne çağırırsa çağırsın) kaynakları aldığında meydana gelir, burada her kaynak bir seferde yalnızca bir iş parçacığı tarafından tutulabilir ve bu kaynakları ayırma önlenemeyecek şekilde tutarsa ​​ve iş parçacıkları arasında bazı "dairesel" ilişki vardır, öyle ki kilitlenmedeki her iş parçacığı başka bir iş parçacığı tarafından tutulan bir miktar kaynağı elde etmeyi beklemektedir.

Öyleyse, kilitlenmeyi önlemenin kolay bir yolu, kaynaklara tam bir sıralama vermek ve kaynakların yalnızca sırayla iş parçacıkları tarafından elde edileceğine dair bir kural koymaktır . Tersine, kaynakları toplayan ancak bunları sırayla almayan iş parçacıkları çalıştırılarak kasıtlı olarak bir kilitlenme oluşturulabilir. Örneğin:

İki iplik, iki kilit. Birinci iş parçacığı, kilitleri belirli bir sırayla edinmeye çalışan bir döngü çalıştırır, ikinci iş parçacığı kilitleri ters sırada almaya çalışan bir döngü çalıştırır. Her iş parçacığı, kilitleri başarıyla aldıktan sonra her iki kilidi de serbest bırakır.

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

Şimdi, bu soruya bir kaç yorum yaşandığını arasındaki fark dışarı noktası olasılığı ve kesinlik çıkmazdan. Bir anlamda, ayrım akademik bir konudur. Pratik açıdan, yukarıda yazdığım kodla kilitlenmeyen çalışan bir sistem görmek isterim :)

Bununla birlikte, mülakat soruları bazen akademik olabilir ve bu GK sorusunun başlıkta "kesinlikle" kelimesi vardır, bu nedenle aşağıdaki, kesinlikle çıkmaza giren bir programdır . İki Lockernesne oluşturulur, her birine iki kilit verilir ve bir CountDownLatchiş parçacığı arasında senkronizasyon için kullanılır. Her biri Lockerilk kilidi kilitler ve ardından mandalı bir kez geri sayar. Her iki diş de bir kilit kazandığında ve mandalı geri saydığında, mandal bariyerini geçer ve ikinci bir kilit elde etmeye çalışır, ancak her durumda diğer iplik zaten istenen kilidi tutar. Bu durum, belirli bir çıkmaza neden olur.

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

3
Linus'tan alıntı yaptığım için üzgünüm, "Konuşmak ucuzdur. Kodu bana göster." - bu güzel bir görev ve göründüğünden şaşırtıcı derecede daha zor.
alf

2
Bu kodu kilitlenmeden çalıştırmak mümkün
Vladimir Zhilyaev

1
Tamam, acımasızsınız, ama bence bu artık tam bir cevap.
Greg Mattes

@GregMattes teşekkürler :) +1 dışında bir şey ekleyemezsiniz ve umarım eğlenmişsinizdir :)
alf

15

İşte Eric Lippert'inkini takip eden bir Java örneği:

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

}

4
Join in run yöntemini kullanmanın biraz yanıltıcı olduğunu düşünüyorum. Statik bloğun yanı sıra bu diğer birleştirmenin bir kilitlenme elde etmek için gerekli olduğunu ve kilitlenmenin "new Lock ()" ifadesinden kaynaklandığını ileri sürer. C # örneğindeki gibi statik yöntemi kullanarak yeniden
yazmam

Örneğinizi açıklayabilir misiniz?
gstackoverflow

deneylerime göre t.join (); inside run () yöntemi gereksiz
gstackoverflow


11

İşte belgelerden bir Örnek :

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

2
+1 Java öğreticisini bağlamak için.
mre

4
"büyük ihtimalle" yeterince iyi değil "kesinlikle çıkmaza girecek"
alf

1
@alf Oh ama temel mesele burada oldukça güzel bir şekilde gösteriliyor. Bir Object invokeAndWait(Callable task)yöntemi ortaya çıkaran bir Round robin zamanlayıcı yazılabilir . Sonra bütün Callable t1yapması gereken invokeAndWait()için Callable t2dönmeden önce yaşam süresi boyunca, ve tersi.
user268396

2
@ user268396 iyi, temel konu önemsiz ve sıkıcı :) Görevin bütün amacı, garantili bir kilitlenmenin (ve eşzamansız bir dünyada herhangi bir şeyi garanti etmenin yanı sıra) şaşırtıcı derecede zor olduğunu bulmak veya anladığınızı kanıtlamaktır. ).
alf

4
@bezz sleepsıkıcı. 5 saniye boyunca hiçbir iş parçacığının başlamayacağına inanmama rağmen, bu bir yarış durumu zaten. sleep()Yarış koşullarını çözmede güvenecek bir programcı kiralamak istemezsiniz :)
alf

9

Eric Lippert tarafından yayınlanan kilitlenme örneğinin Yuriy Zubarev'in Java sürümünü yeniden yazdım: https://stackoverflow.com/a/9286697/2098232 C # sürümüne daha çok benzeyecek şekilde. Java'nın başlatma bloğu C # statik yapıcısına benzer şekilde çalışırsa ve ilk önce kilidi elde ederse, bir kilitlenme elde etmek için birleştirme yöntemini de çağırmak için başka bir iş parçacığına ihtiyacımız yoktur, yalnızca Lock sınıfından, orijinal C # gibi bazı statik yöntemi çağırması gerekir. misal. Ortaya çıkan kilitlenme bunu doğruluyor gibi görünüyor.

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");
    }

}

neden çalıştırma yönteminde Lock.initialize () 'yi yorumladığımda kilitlenmiyor? başlatma yöntemi yine de hiçbir şey yapmıyor?
Aequitas

@Aequitas sadece bir tahmin ancak yöntem uzakta optimize edilebilir; bunun
konularla

5

Bu, alabileceğiniz en basit bir röportaj görevi değil: benim projemde, bir takımın çalışmasını bütün gün felç etti. Programınızı durdurmak çok kolaydır, ancak onu thread dump'ın şöyle bir şey yazdığı duruma getirmek çok zordur:

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)

Dolayısıyla amaç, JVM'nin bir kilitlenme olarak değerlendireceği bir çıkmaza girmek olacaktır. Açıkçası, hiçbir çözüm yok

synchronized (this) {
    wait();
}

sonsuza kadar duracak olsalar bile bu anlamda çalışacaklardır. Bir yarış durumuna güvenmek de iyi bir fikir değildir, çünkü mülakat sırasında çoğu zaman işe yaraması gereken bir şeyi değil, kanıtlanabilir şekilde çalışan bir şeyi göstermek istersiniz.

Şimdi, sleep()çözüm tamam, bir anlamda işe yaramadığı, ancak adil olmadığı bir durumu hayal etmek zor (adil bir spordayız, değil mi?). @Artbristol'ün çözümü (benimki aynı, sadece monitörlerle farklı nesneler) güzel, ancak uzun ve yeni eşzamanlılık ilkellerini kullanarak iş parçacıkları doğru duruma getiriliyor , ki bu o kadar da eğlenceli değil:

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) {
            }
        }
    }
}

Tek synchronizedçözümün 11..13 satır koda uyduğunu (yorumlar ve içe aktarmalar hariç), ancak asıl hileyi henüz hatırlamadığını hatırlıyorum. Yaparsam güncellenecek.

Güncelleme: İşte çirkin bir çözüm 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) {
            }
        }
    }
}

Bir mandalı bir nesne monitörüyle değiştirdiğimize dikkat edin ( "a"nesne olarak kullanarak ).


Bence bu adil bir röportaj görevi. Java'daki kilitlenmeleri ve kilitlenmeleri gerçekten anlamanızı ister. Genel fikrin de o kadar zor olduğunu sanmıyorum (her iki iş parçacığının da ancak her ikisinin de ilk kaynaklarını kilitledikten sonra devam edebileceğinden emin olun), sadece CountdownLatch'i hatırlamalısınız - ancak görüşmeci olarak görüşmeyi yapan kişiye o kısımda yardım ederdim tam olarak neye ihtiyacı olduğunu açıklayabilirse (bu, çoğu geliştiricinin ihtiyaç duyduğu bir sınıf değildir ve bir röportajda bunun için google'da arama yapamazsınız). Röportajlar için bu kadar ilginç sorular almayı çok isterim!
Voo

@Voo onunla oynadığımız sırada JDK'da mandal yoktu, bu yüzden hepsi elle yapıldı. Ve arasındaki fark LOCKEDve waiting to lockkahvaltıda sırasında okunan şey ince değil. Ama muhtemelen haklısın. Yeniden ifade edeyim.
alf

4

Bu C # sürümü, sanırım java oldukça benzer olmalı.

static void Main(string[] args)
{
    var mainThread = Thread.CurrentThread;
    mainThread.Join();

    Console.WriteLine("Press Any key");
    Console.ReadKey();
}

2
İyi bir! consoleİfadeleri kaldırırsanız bir kilitlenme oluşturmak için gerçekten en kısa C # programı . Tüm Mainişlevi olarak basitçe yazabilirsiniz Thread.CurrentThread.Join();.
RBT

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

Program her zaman kilitlenir çünkü her bir iş parçacığı diğer iş parçacıkları için bariyerde beklemektedir, ancak engeli beklemek için iş parçacığının monitörü açık tutması gerekir BadRunnable.class.


3
} catch (InterruptedException ex) { continue; }... güzel
artbristol

2

Burada Java'da bir örnek var

http://baddotrobot.com/blog/2009/12/24/deadlock/

Bir kaçıranın kurbanı nakit parayı alana kadar vermeyi reddetmesi, ancak müzakerecinin kurbanı alana kadar parayı vermeyi reddetmesi durumunda çıkmaza girdiği yer.


Bu uygulama verildiği gibi uygun değildir. Bazı kod parçaları eksik görünüyor. Bununla birlikte, ifade ettiğiniz genel fikir, çıkmaza yol açan kaynak çekişmesi söz konusu olduğunda doğrudur.
Master Chief

örnek pedagojiktir, bu yüzden onu neden uygun değil olarak yorumladığınızı merak ediyorum ... eksik kod, yöntem adlarının yardımcı olması gereken (ancak kısalık için gösterilmediği) boş yöntemlerdir
Toby

1

Basit bir arama bana şu kodu verdi:

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

Kaynak: Kilitlenme


3
"büyük olasılıkla" yeterince iyi değil "kesinlikle çıkmaza girecek"
alf

1

İşte bir iplik tutma kilidinin aynı kilidi isteyen başka bir iş parçacığını başlattığı ve ardından başlatıcının başlayana kadar beklediği örnek ... sonsuza kadar:

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

0

İşte bir örnek:

iki iş parçacığı çalışıyor, her biri diğerinin kilidi açmasını bekliyor

public class ThreadClass, Konu {

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

    }


}

}

Bunu çalıştırmak kilitlenmeye neden olur:

public class 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);


}

}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.