Skenario sederhana menggunakan wait () dan notify () di java

181

Bisakah saya mendapatkan skenario sederhana lengkap yaitu tutorial yang menyarankan bagaimana ini harus digunakan, khususnya dengan Antrian?

Olaseni
sumber

Jawaban:

269

Metode wait()dan notify()dirancang untuk menyediakan mekanisme untuk memungkinkan thread untuk memblokir sampai kondisi tertentu terpenuhi. Untuk ini saya menganggap Anda ingin menulis implementasi antrian pemblokiran, di mana Anda memiliki beberapa elemen penyimpanan cadangan ukuran tetap.

Hal pertama yang harus Anda lakukan adalah mengidentifikasi kondisi yang Anda inginkan metode menunggu. Dalam hal ini, Anda ingin put()metode untuk memblokir sampai ada ruang kosong di toko, dan Anda ingin take()metode untuk memblokir hingga ada beberapa elemen untuk kembali.

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

Ada beberapa hal yang perlu diperhatikan tentang cara Anda harus menggunakan mekanisme menunggu dan memberi tahu.

Pertama, Anda perlu memastikan bahwa panggilan apa pun ke wait()atau notify()berada dalam wilayah kode yang disinkronkan (dengan wait()dan notify()panggilan disinkronkan pada objek yang sama). Alasan untuk ini (selain masalah keamanan ulir standar) adalah karena sesuatu yang dikenal sebagai sinyal yang terlewat.

Contoh dari ini, adalah bahwa sebuah thread dapat memanggil put()ketika antrian kebetulan penuh, kemudian memeriksa kondisi, melihat bahwa antrian penuh, namun sebelum dapat memblokir utas lain dijadwalkan. Utas kedua ini lalu take()elemen dari antrian, dan memberi tahu utas tunggu bahwa antrian tidak lagi penuh. Namun, karena utas pertama sudah memeriksa kondisinya, ia hanya akan memanggil wait()setelah dijadwalkan ulang, meskipun itu dapat membuat kemajuan.

Dengan menyinkronkan pada objek bersama, Anda dapat memastikan bahwa masalah ini tidak terjadi, karena take()panggilan utas kedua tidak akan dapat membuat kemajuan sampai utas pertama benar-benar diblokir.

Kedua, Anda perlu menempatkan kondisi yang Anda periksa dalam loop sementara, bukan pernyataan if, karena masalah yang dikenal sebagai wake-up palsu. Di sinilah utas tunggu kadang-kadang dapat diaktifkan kembali tanpa notify()dipanggil. Menempatkan pemeriksaan ini dalam loop sementara akan memastikan bahwa jika wake-up palsu terjadi, kondisinya akan diperiksa ulang, dan utas akan menelepon wait()lagi.


Seperti beberapa jawaban lain yang disebutkan, Java 1.5 memperkenalkan perpustakaan konkurensi baru (dalam java.util.concurrentpaket) yang dirancang untuk memberikan abstraksi tingkat yang lebih tinggi atas mekanisme menunggu / memberi tahu. Dengan menggunakan fitur-fitur baru ini, Anda dapat menulis ulang contoh aslinya seperti:

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

Tentu saja jika Anda benar-benar membutuhkan antrian pemblokiran, maka Anda harus menggunakan implementasi antarmuka BlockingQueue .

Juga, untuk hal-hal seperti ini saya sangat merekomendasikan Java Concurrency in Practice , karena mencakup semua yang Anda ingin tahu tentang masalah dan solusi terkait concurrency.

Jared Russell
sumber
7
@ Selamat, notifybangun hanya satu utas. Jika dua utas konsumen bersaing untuk menghapus suatu elemen, satu pemberitahuan dapat membangunkan utas konsumen lainnya, yang tidak dapat berbuat apa-apa dan akan kembali tidur (alih-alih produsen, yang kami harapkan akan memasukkan elemen baru.) Karena utas produser tidak dibangunkan, tidak ada yang dimasukkan dan sekarang ketiga utas akan tidur tanpa batas. Saya menghapus komentar saya sebelumnya karena dikatakan (salah) bahwa bangun palsu adalah penyebab masalah (Tidak.)
finnw
1
@ finnw Sejauh yang saya tahu, masalah yang Anda temukan dapat diselesaikan dengan menggunakan notifyAll (). Apakah saya benar?
Clint Eastwood
1
Contoh yang diberikan di sini oleh @ Jared cukup bagus tetapi memiliki kejatuhan yang serius. Dalam kode semua metode telah ditandai sebagai disinkronkan, tetapi TIDAK ADA DUA METODE yang disinkronkan DAPAT DIEKSEKUSI PADA WAKTU YANG SAMA, lalu kenapa ada utas kedua dalam gambar.
Shivam Aggarwal
10
@ Brut3Forc3 Anda perlu membaca javadoc of wait (): katanya: Thread melepaskan kepemilikan monitor ini . Jadi, begitu wait () dipanggil, monitor dilepaskan, dan utas lainnya dapat menjalankan metode antrian lain yang disinkronkan.
JB Nizet
1
@JBNizet. "Sebuah contoh dari ini, adalah bahwa sebuah thread dapat memanggil put () ketika antrian kebetulan penuh, kemudian memeriksa kondisinya, melihat bahwa antrian penuh, namun sebelum dapat memblokir thread lain dijadwalkan". Inilah kenapa utas kedua dijadwalkan jika menunggu belum dipanggil
Shivam Aggarwal
148

Bukan contoh antrian, tapi sangat sederhana :)

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

Beberapa poin penting:
1) TIDAK PERNAH lakukan

 if(!pizzaArrived){
     wait();
 }

Selalu gunakan saat (kondisi), karena

  • a) utas dapat secara sporadis terbangun dari keadaan menunggu tanpa diberi tahu oleh siapa pun. (Bahkan ketika orang pizza tidak membunyikan lonceng, seseorang akan memutuskan mencoba makan pizza.).
  • b) Anda harus memeriksa kondisinya lagi setelah mendapatkan kunci yang disinkronkan. Katakanlah pizza tidak bertahan selamanya. Anda bangun, berbaris untuk pizza, tetapi itu tidak cukup untuk semua orang. Jika Anda tidak memeriksa, Anda mungkin makan kertas! :) (mungkin contoh yang lebih baik while(!pizzaExists){ wait(); }.

2) Anda harus memegang kunci (disinkronkan) sebelum memanggil tunggu / nofity. Thread juga harus mendapatkan kunci sebelum bangun.

3) Cobalah untuk menghindari memperoleh kunci apa pun di dalam blok Anda yang disinkronkan dan berusaha untuk tidak memanggil metode asing (metode yang Anda tidak tahu pasti apa yang mereka lakukan). Jika Anda harus, pastikan untuk mengambil langkah-langkah untuk menghindari kebuntuan.

4) Hati-hati dengan memberi tahu (). Tetap dengan notifyAll () sampai Anda tahu apa yang Anda lakukan.

5) Terakhir, namun tidak kalah pentingnya, baca Java Concurrency in Practice !

Enno Shioji
sumber
1
Bisakah Anda menjelaskan mengapa tidak menggunakan "if (! PizzaArrived) {wait ();}"?
Semua orang
2
@Setiap orang: Menambahkan beberapa penjelasan. HTH.
Enno Shioji
1
mengapa menggunakan pizzaArrivedbendera? jika bendera diubah tanpa panggilan ke notifysana tidak akan berpengaruh. Juga hanya dengan waitdan notifymemanggil contoh berfungsi.
Pablo Fernandez
2
Saya tidak mengerti - utas 1 mengeksekusi metode eatPizza () dan memasuki blok tersinkronisasi atas, dan menyinkronkan pada kelas MyHouse. Tidak ada pizza yang telah tiba sehingga hanya menunggu. Sekarang utas 2 mencoba mengirimkan pizza dengan memanggil metode pizzaGuy (); tetapi tidak bisa karena utas 1 sudah memiliki kunci dan tidak melepaskannya (selalu menunggu). Secara efektif hasilnya adalah jalan buntu - utas 1 sedang menunggu utas 2 untuk menjalankan metode notifyAll (), sedangkan utas 2 sedang menunggu utas 1 untuk memberikan kunci pada kelas MyHouse ... Apa yang saya lewatkan sini?
flamming_python
1
Tidak, ketika sebuah variabel dijaga oleh synchronizedkata kunci, itu berlebihan untuk mendeklarasikan variabel volatile, dan direkomendasikan untuk menghindarinya untuk menghindari kebingungan @mrida
Enno Shioji
37

Meskipun Anda meminta wait()dan notify()secara spesifik, saya merasa bahwa kutipan ini masih cukup penting:

Josh Bloch, Java Edisi 2 Efektif , Butir 69: Memilih utilitas konkurensi ke waitdan notify(penekanannya):

Mengingat sulitnya menggunakan waitdan notifydengan benar, Anda harus menggunakan utilitas konkurensi tingkat tinggi sebagai gantinya [...] menggunakan waitdan notifysecara langsung seperti pemrograman dalam "bahasa perakitan konkurensi", dibandingkan dengan bahasa tingkat yang lebih tinggi yang disediakan oleh java.util.concurrent. Jarang, jika pernah, alasan untuk menggunakan waitdan notifydalam kode baru .

polygenelubricants
sumber
BlockingQueueS yang disediakan dalam paket java.util.concurrent tidak persisten. Apa yang bisa kita gunakan ketika antrian harus gigih? yaitu jika sistem turun dengan 20 item dalam antrian saya perlu mereka hadir ketika sistem restart. Karena semua java.util.concurrent queue nampaknya 'in memory' hanya adakah cara ini dapat digunakan seperti / hacked / override untuk menyediakan implementasi yang mampu bertahan?
Volksman
1
Mungkin antrian pendukung dapat disediakan? yaitu kami akan menyediakan implementasi antarmuka Antrian yang persisten.
Volksman
Ini sangat baik untuk disebutkan dalam konteks ini bahwa Anda tidak perlu menggunakan notify()dan wait()lagi
Chaklader Asfak Arefe
7

Sudahkah Anda melihat Tutorial Java ini ?

Lebih lanjut, saya menyarankan Anda untuk menjauh dari bermain dengan hal-hal semacam ini dalam perangkat lunak nyata. Baik untuk bermain dengannya sehingga Anda tahu apa itu, tetapi concurrency memiliki jebakan di semua tempat. Lebih baik menggunakan abstraksi level yang lebih tinggi dan koleksi yang disinkronkan atau antrian JMS jika Anda membuat perangkat lunak untuk orang lain.

Setidaknya itulah yang saya lakukan. Saya bukan ahli konkurensi jadi saya tinggal menangani benang dengan tangan sedapat mungkin.

ekstraneon
sumber
2

Contoh

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
     }

}
Ferhat KOÇER
sumber
0

Contoh untuk wait () dan notifyall () di Threading.

Daftar array statis tersinkronisasi digunakan sebagai sumber daya dan metode menunggu () dipanggil jika daftar array kosong. metode notify () dipanggil setelah elemen ditambahkan untuk daftar array.

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());
   }
}
srinivas
sumber
1
tlg periksa baris ini if(arrayList.size() == 0), saya pikir itu mungkin kesalahan di sini.
Wizmann