Java'da wait () ve notify () yöntemini kullanan basit bir senaryo


181

Tam bir basit senaryo alabilir miyim, özellikle bunun bir Kuyruk ile nasıl kullanılması gerektiğini öneren öğretici?

Yanıtlar:


269

wait()Ve notify()yöntem, belirli bir koşul karşılanmış oluncaya dek bloğa bir iplik izin vermek için bir mekanizma sağlamak üzere tasarlanmıştır. Bunun için bazı sabit boyutlu destek deponuzun bulunduğu bir engelleme kuyruğu uygulaması yazmak istediğinizi varsayalım.

Yapmanız gereken ilk şey, yöntemlerin beklemesini istediğiniz koşulları tanımlamaktır. Bu durumda, put()depoda boş alan take()olana kadar yöntemin engellenmesini ve döndürülecek bir öğe olana kadar yöntemin engellenmesini istersiniz .

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while(queue.size() == capacity) {
            wait();
        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

Bekleme ve bildirim mekanizmalarını nasıl kullanmanız gerektiği hakkında dikkat edilmesi gereken birkaç nokta vardır.

Öncelikle, kodun senkronize edilmiş bir bölgesine ( veya çağrılarının aynı nesne üzerinde senkronize edilmesiyle) eşitlenmiş wait()veya notify()içinde olduğundan emin olmanız gerekir . Bunun nedeni (standart iplik güvenliği endişeleri dışında), kaçırılan sinyal olarak bilinen bir şeyden kaynaklanmaktadır.wait()notify()

Bunun bir örneği put(), kuyruk bir dolu olduğunda bir iş parçacığının arayabileceği , daha sonra koşulu kontrol edebileceği, kuyruğun dolu olduğunu, ancak başka bir iş parçacığını engelleyebilmesinden önce programlanabileceğidir. Bu ikinci iş parçacığı take(), kuyruktaki bir öğedir ve bekleyen iş parçacıklarının artık dolu olmadığını bildirir. Bununla birlikte, ilk iş parçacığı durumu zaten kontrol ettiğinden, wait()ilerleme kaydedebilse bile yeniden programlandıktan sonra arayacaktır .

İkinci bir iş parçacığının take()çağrısı, ilk iş parçacığı gerçekten engellenene kadar ilerleme sağlayamayacağından , paylaşılan bir nesne üzerinde eşitleme yaparak bu sorunun oluşmamasını sağlayabilirsiniz.

İkinci olarak, sahte uyandırma olarak bilinen bir sorun nedeniyle, if ifadesi yerine bir while döngüsüne kontrol ettiğiniz koşulu koymanız gerekir. Burada bekleyen bir iş parçacığı bazen notify()çağrılmadan yeniden etkinleştirilebilir . Bu kontrolü bir while döngüsüne koymak, sahte bir uyandırma gerçekleşirse durumun tekrar kontrol edilmesini ve iş parçacığının tekrar aranmasını sağlar wait().


Diğer bazı cevapların da belirttiği gibi, Java 1.5 java.util.concurrent, bekleme / bildirim mekanizması üzerinde daha yüksek bir soyutlama sağlamak için tasarlanmış yeni bir eşzamanlılık kütüphanesi ( pakette) tanıttı . Bu yeni özellikleri kullanarak orijinal örneği şu şekilde yeniden yazabilirsiniz:

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            while(queue.size() == capacity) {
                notFull.await();
            }

            queue.add(element);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while(queue.isEmpty()) {
                notEmpty.await();
            }

            T item = queue.remove();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Tabii ki aslında bir engelleme sırasına ihtiyacınız varsa, o zaman BlockingQueue arabiriminin bir uygulamasını kullanmalısınız .

Ayrıca, bunun gibi şeyler için , uygulamada Java Concurrency'u tavsiye ederim , çünkü eşzamanlılık ile ilgili sorunlar ve çözümleri hakkında bilmek isteyebileceğiniz her şeyi kapsar.


7
@greuze, notifysadece bir iş parçacığını uyandırır. İki tüketici iş parçacığı bir öğeyi kaldırmak için yarışıyorsa, bir bildirim diğer tüketici iş parçacığını uyandırabilir, bu da onun hakkında hiçbir şey yapamaz ve uykuya geri döner (üreticinin yerine yeni bir öğe eklemesini umuyoruz). Çünkü üretici iş parçacığı uyandırılmaz, hiçbir şey sokulmaz ve şimdi üç iş parçacığı da süresiz olarak uyur. Sahte uyandırmanın sorunun nedeni olduğunu söylediğim gibi (yanlış değil) önceki
yorumumu kaldırdım

1
@finnw Anlayabildiğim kadarıyla, tespit ettiğiniz sorun notifyAll () kullanılarak çözülebilir. Haklı mıyım?
Clint Eastwood

1
@Jared tarafından verilen örnek oldukça iyi ancak ciddi bir düşüş var. Kodda tüm yöntemler senkronize olarak işaretlenmiştir, ancak AYNI ZAMANDA İKİ SENKRONİZE YÖNTEM UYGULANAMAZ, sonra nasıl resimdeki ikinci iplik vardır.
Shivam Aggarwal

10
@ Brut3Forc3 wait () ifadesinin javadosunu okumalısınız: yazıyor: İş parçacığı bu monitörün sahipliğini serbest bırakır . Bu nedenle, wait () çağrıldığında monitör serbest bırakılır ve başka bir iş parçacığı sıranın başka bir senkronize yöntemini yürütebilir.
JB Nizet

1
@JBNizet. "Bunun bir örneği, sıra dolu olduğunda bir iş parçacığının put () 'ı çağırabilmesidir, daha sonra koşulu kontrol eder, sıranın dolu olduğunu görür, ancak başka bir iş parçacığını engelleyebilmeden önce zamanlanır." bekleme henüz çağrılmadıysa ikinci iş parçacığı programlanır
Shivam Aggarwal

148

Bir kuyruk örneği değil, ama son derece basit :)

class MyHouse {
    private boolean pizzaArrived = false;

    public void eatPizza(){
        synchronized(this){
            while(!pizzaArrived){
                wait();
            }
        }
        System.out.println("yumyum..");
    }

    public void pizzaGuy(){
        synchronized(this){
             this.pizzaArrived = true;
             notifyAll();
        }
    }
}

Bazı önemli noktalar:
1) ASLA yapmayın

 if(!pizzaArrived){
     wait();
 }

Her zaman while (koşul) kullanın, çünkü

  • a) evreler, kimse tarafından bildirilmeksizin, bekleme durumundan ara sıra uyanabilir. (pizza adamı zil çalmadığında bile, biri pizza yemeye karar verirdi.).
  • b) Senkronize kilidi aldıktan sonra durumu tekrar kontrol etmelisiniz. Diyelim ki pizza sonsuza kadar sürmez. Uyanık, pizza için sıraya gir, ama herkes için yeterli değil. Kontrol etmezseniz kağıt yiyebilirsiniz! :) (muhtemelen daha iyi bir örnek olur while(!pizzaExists){ wait(); }.

2) Bekleme / nofity çağırmadan önce kilidi (senkronize) tutmanız gerekir. İplikler ayrıca uyanmadan önce kilit almak zorundadır.

3) Senkronize bloğunuzda herhangi bir kilit almaktan kaçının ve yabancı yöntemleri (ne yaptığını bilmediğiniz yöntemler bilmediğiniz yöntemler) çağırmamaya çalışın. Gerekirse, kilitlenmeleri önlemek için önlem aldığınızdan emin olun.

4) Bildirime () dikkat edin. Ne yaptığınızı bilinceye kadar notifyAll () ile devam edin.

5) Son olarak, fakat aynı derecede önemli olarak , Uygulamada Java Eşzamanlılığı'nı okuyun !


1
Lütfen "if (! PizzaArrived) {wait ();}" kullanmama nedenini biraz açıklayabilir misiniz?
herkes

2
@ Herkes: Bazı açıklamalar eklendi. HTH.
Enno Shioji

1
neden pizzaArrivedbayrağı kullanıyorsunuz ? bayrak çağrılmadan değiştirilirse notify, herhangi bir etkisi olmaz. Ayrıca sadece birlikte waitve notifyörnek eserler çağırır.
Pablo Fernandez

2
Anlamıyorum - iş parçacığı 1 eatPizza () yöntemini yürütür ve üst eşitlenmiş bloğa girer ve MyHouse sınıfında eşitler. Hiçbir pizza henüz geldi, bu yüzden sadece bekler. Şimdi iş parçacığı 2 pizzaGuy () yöntemini çağırarak pizza teslim etmeye çalışır; ancak konu 1 zaten kilidi sahibi olamaz ve vazgeçmiyor (sürekli bekliyor). Etkili bir sonuç kilitlenmesidir - iş parçacığı 1, iş parçacığı 2 MyHouse sınıfındaki kilidi bırakmak için iş parçacığı 2 notifyAll () yöntemini yürütmek için bekliyor ... Ne eksik budur buraya?
flamming_python

1
Hayır, bir değişken synchronizedanahtar kelime ile korunduğunda , değişkeni bildirmek gereksizdir volatileve karışıklığı önlemek için bundan kaçınılması önerilir mrida
Enno Shioji

37

Siz istediniz wait()ve notify()özellikle, bu teklifin hala yeterince önemli olduğunu hissediyorum:

Josh Bloch, Etkili Java 2.Baskı, Öğe 69: Eşzamanlılık yardımcı programlarını tercih edin waitve notify(vurgulayın):

Kullanmanın zorluğu waitve notifydoğru göz önüne alındığında, bunun yerine daha üst düzey eşzamanlılık yardımcı programlarını kullanmalısınız [...] waitve notifydoğrudan sağlanan "eşzamanlılık dilini" programlama ile benzerdir java.util.concurrent. Nadiren de olsa kullanım kodu waitve notifyyeni kod vardır .


Java.util.concurrent paketinde sağlanan BlockingQueueS kalıcı değil. Kuyruğun kalıcı olması gerektiğinde ne kullanabiliriz? yani sistem kuyrukta 20 öğe ile aşağı giderse sistem yeniden başlatıldığında mevcut olması gerekir. Java.util.concurrent sıralarının hepsi 'bellekte' göründüğü için, bunların kalıcılık özellikli uygulamalar sağlamak için / hack / geçersiz kılınmış olarak kullanılabilmesinin herhangi bir yolu var mı?
Volksman

1
Belki destek kuyruğu sağlanabilir mi? yani, kalıcı bir Queue arayüzü uygulaması sağlayacağız.
Volksman

Bu kullanmaya gerek asla bu bağlamda söz etmek çok iyidir notify()ve wait()tekrar
Chaklader ASFak arefe

7

Bu Java Eğiticisine bir göz attınız mı?

Ayrıca, gerçek yazılımda bu tür şeylerle oynamaktan uzak durmanızı tavsiye ederim. Onunla oynamak güzel, bu yüzden ne olduğunu biliyorsun, ama eşzamanlılığın her yerinde tuzaklar var. Başkaları için yazılım oluşturuyorsanız daha üst düzey soyutlamalar ve senkronize edilmiş koleksiyonlar veya JMS kuyrukları kullanmak daha iyidir.

En azından benim yaptığım bu. Bir eşzamanlılık uzmanı değilim, bu yüzden mümkün olan her yerde iş parçacıklarını elle işlemekten uzak duruyorum.


2

Misal

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla bla bla
        }
     }
     public synchronized void threadCondWait(){
        while(myCondition){
           wait();//Comminucate with notify()
        }
     }

}
public class myAnotherThread extends Thread{
     @override
     public void run(){
        //Bla Bla bla
        notify();//Trigger wait() Next Step
     }

}

0

İş Parçacığındaki wait () ve notifyall () örneği.

Senkronize edilmiş statik dizi listesi kaynak olarak kullanılır ve dizi listesi boşsa wait () yöntemi çağrılır. dizi listesi için bir öğe eklendiğinde notify () yöntemi çağrılır.

public class PrinterResource extends Thread{

//resource
public static List<String> arrayList = new ArrayList<String>();

public void addElement(String a){
    //System.out.println("Add element method "+this.getName());
    synchronized (arrayList) {
        arrayList.add(a);
        arrayList.notifyAll();
    }
}

public void removeElement(){
    //System.out.println("Remove element method  "+this.getName());
    synchronized (arrayList) {
        if(arrayList.size() == 0){
            try {
                arrayList.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            arrayList.remove(0);
        }
    }
}

public void run(){
    System.out.println("Thread name -- "+this.getName());
    if(!this.getName().equalsIgnoreCase("p4")){
        this.removeElement();
    }
    this.addElement("threads");

}

public static void main(String[] args) {
    PrinterResource p1 = new PrinterResource();
    p1.setName("p1");
    p1.start();

    PrinterResource p2 = new PrinterResource();
    p2.setName("p2");
    p2.start();


    PrinterResource p3 = new PrinterResource();
    p3.setName("p3");
    p3.start();


    PrinterResource p4 = new PrinterResource();
    p4.setName("p4");
    p4.start();     

    try{
        p1.join();
        p2.join();
        p3.join();
        p4.join();
    }catch(InterruptedException e){
        e.printStackTrace();
    }
    System.out.println("Final size of arraylist  "+arrayList.size());
   }
}

1
plz bu satırı çift kontrol if(arrayList.size() == 0), ben burada bir hata olabilir düşünüyorum.
Wizmann
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.