Jawaban:
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.concurrent
paket) 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.
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
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 !
pizzaArrived
bendera? jika bendera diubah tanpa panggilan ke notify
sana tidak akan berpengaruh. Juga hanya dengan wait
dan notify
memanggil contoh berfungsi.
synchronized
kata kunci, itu berlebihan untuk mendeklarasikan variabel volatile
, dan direkomendasikan untuk menghindarinya untuk menghindari kebingungan @mrida
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 wait
dan notify
(penekanannya):
Mengingat sulitnya menggunakan
wait
dannotify
dengan benar, Anda harus menggunakan utilitas konkurensi tingkat tinggi sebagai gantinya [...] menggunakanwait
dannotify
secara langsung seperti pemrograman dalam "bahasa perakitan konkurensi", dibandingkan dengan bahasa tingkat yang lebih tinggi yang disediakan olehjava.util.concurrent
. Jarang, jika pernah, alasan untuk menggunakanwait
dannotify
dalam kode baru .
notify()
dan wait()
lagi
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.
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
}
}
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());
}
}
if(arrayList.size() == 0)
, saya pikir itu mungkin kesalahan di sini.
notify
bangun 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.)