Risposte:
I metodi wait()
e notify()
sono progettati per fornire un meccanismo per consentire a un thread di bloccarsi fino a quando non viene soddisfatta una condizione specifica. Per questo suppongo che tu voglia scrivere un'implementazione della coda di blocco, in cui hai un back-store di elementi di dimensioni fisse.
La prima cosa che devi fare è identificare le condizioni che desideri che i metodi aspettino. In questo caso, vorrai che il put()
metodo si blocchi fino a quando non c'è spazio libero nell'archivio e vorrai che il take()
metodo si blocchi fino a quando non ci sarà qualche elemento da restituire.
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;
}
}
Vi sono alcune cose da notare sul modo in cui è necessario utilizzare i meccanismi di attesa e notifica.
In primo luogo, è necessario assicurarsi che tutte le chiamate verso wait()
o notify()
siano all'interno di un'area di codice sincronizzata (con le chiamate wait()
e notify()
sincronizzate sullo stesso oggetto). La ragione di ciò (oltre alle preoccupazioni sulla sicurezza del thread standard) è dovuta a qualcosa noto come segnale perso.
Un esempio di ciò è che un thread può chiamare put()
quando la coda sembra essere piena, quindi controlla la condizione, vede che la coda è piena, tuttavia prima che possa bloccare è programmato un altro thread. Questo secondo thread è quindi take()
un elemento della coda e notifica ai thread in attesa che la coda non è più piena. Poiché il primo thread ha già verificato la condizione, chiamerà semplicemente wait()
dopo essere stato riprogrammato, anche se potrebbe fare progressi.
Sincronizzando un oggetto condiviso, è possibile assicurarsi che questo problema non si verifichi, poiché la take()
chiamata del secondo thread non sarà in grado di avanzare fino a quando il primo thread non si sarà effettivamente bloccato.
In secondo luogo, è necessario inserire la condizione che si sta verificando in un ciclo while, piuttosto che un'istruzione if, a causa di un problema noto come risvegli spuri. È qui che a volte un thread in attesa può essere riattivato senza notify()
essere chiamato. Mettendo questo controllo in un ciclo while si assicurerà che se si verifica un risveglio spuria, la condizione verrà ricontrollata e il thread chiamerà di wait()
nuovo.
Come alcune delle altre risposte menzionate, Java 1.5 ha introdotto una nuova libreria di concorrenza (nel java.util.concurrent
pacchetto) progettata per fornire un'astrazione di livello superiore rispetto al meccanismo di attesa / notifica. Utilizzando queste nuove funzionalità, è possibile riscrivere l'esempio originale in questo modo:
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();
}
}
}
Naturalmente se in realtà è necessaria una coda di blocco, è necessario utilizzare un'implementazione dell'interfaccia BlockingQueue .
Inoltre, per cose come questa, consiglio vivamente la concorrenza Java in pratica , poiché copre tutto ciò che potresti voler sapere su problemi e soluzioni relativi alla concorrenza.
Non un esempio di coda, ma estremamente semplice :)
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();
}
}
}
Alcuni punti importanti:
1) MAI farlo
if(!pizzaArrived){
wait();
}
Utilizzare sempre while (condizione), perché
while(!pizzaExists){ wait(); }
.2) È necessario tenere il blocco (sincronizzato) prima di invocare wait / nofity. I thread devono anche acquisire il blocco prima di svegliarsi.
3) Cerca di evitare di acquisire alcun blocco all'interno del tuo blocco sincronizzato e cerca di non invocare metodi alieni (metodi che non conosci con certezza cosa stanno facendo). Se è necessario, assicurarsi di adottare misure per evitare deadlock.
4) Prestare attenzione con notify (). Rimani con NotifyAll () fino a quando non sai cosa stai facendo.
5) Ultimo, ma non meno importante, leggi Java Concurrency in Practice !
pizzaArrived
bandiera? se il flag viene cambiato senza una chiamata notify
, non avrà alcun effetto. Anche solo con wait
e notify
chiama l'esempio funziona.
synchronized
parola chiave, è ridondante dichiararla volatile
e si consiglia di evitarla per evitare confusione @mrida
Anche se hai chiesto wait()
e in notify()
particolare, ritengo che questa citazione sia ancora abbastanza importante:
Josh Bloch, Effective Java 2nd Edition , Articolo 69: Preferisco utilità concorrenza per wait
e notify
(enfasi la sua):
Data la difficoltà di utilizzare
wait
enotify
correttamente, è necessario utilizzare le utilità di concorrenza di livello superiore invece [...] utilizzandowait
enotify
direttamente è come programmare in "linguaggio assembly di concorrenza", rispetto al linguaggio di livello superiore fornito dajava.util.concurrent
. C'è raramente, se mai, motivo per usarewait
enotify
nel nuovo codice .
notify()
e wait()
ancora una volta
Hai dato un'occhiata a questo tutorial Java ?
Inoltre, ti consiglierei di stare lontana dal giocare con questo tipo di cose con un vero software. È bello giocarci in modo da sapere di cosa si tratta, ma la concorrenza ha insidie dappertutto. È meglio utilizzare astrazioni di livello superiore e raccolte sincronizzate o code JMS se si sta creando software per altre persone.
Questo è almeno quello che faccio. Non sono un esperto di concorrenza, quindi sto lontano dalla gestione dei thread a mano, ove possibile.
Esempio
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
}
}
Esempio di wait () e notifyall () in Threading.
Un elenco di array statico sincronizzato viene utilizzato come metodo resource e wait () viene chiamato se l'elenco di array è vuoto. Il metodo notify () viene richiamato una volta aggiunto un elemento per l'elenco di 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)
, penso che potrebbe essere un errore qui.
notify
sveglia solo un thread. Se due thread di consumatori sono in competizione per rimuovere un elemento, uno di notifica potrebbe riattivare l'altro thread di consumatore, che non può farci nulla e tornerà a dormire (invece del produttore, che speravamo potesse inserire un nuovo elemento.) Perché il thread del produttore non viene svegliato, non viene inserito nulla e ora tutti e tre i thread dormiranno indefinitamente. Ho rimosso il mio commento precedente in quanto diceva (a torto) che il risveglio spurio era la causa del problema (non lo è.)