Odpowiedzi:
wait()
Oraz notify()
sposoby mają na celu zapewnić mechanizm pozwalający gwint do bloku, aż stan specyficzne spełnione. W tym celu zakładam, że chcesz napisać implementację kolejki blokującej, w której masz zapasowy zapas elementów o stałym rozmiarze.
Pierwszą rzeczą, którą musisz zrobić, to określić warunki, na które metody mają czekać. W takim przypadku będziesz chciał, aby put()
metoda blokowała, dopóki nie będzie wolnego miejsca w sklepie, i będziesz chciał, aby take()
metoda blokowała, dopóki jakiś element nie zwróci.
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;
}
}
Należy zwrócić uwagę na kilka sposobów korzystania z mechanizmów oczekiwania i powiadamiania.
Po pierwsze, należy upewnić się, że wszelkie wywołania wait()
lub notify()
zsynchronizowane regiony kodu ( wait()
lub notify()
wywołania i są synchronizowane na tym samym obiekcie). Przyczyną tego (poza standardowymi obawami dotyczącymi bezpieczeństwa wątków) jest coś, co nazywane jest brakującym sygnałem.
Przykładem tego jest to, że wątek może wywoływać, put()
gdy kolejka się zapełni, a następnie sprawdza warunek, widzi, że kolejka jest pełna, jednak zanim będzie mogła zablokować inny wątek, zaplanowano. Ten drugi wątek jest wówczas take()
elementem z kolejki i powiadamia oczekujące wątki, że kolejka nie jest już pełna. Ponieważ pierwszy wątek już sprawdził ten warunek, po prostu zadzwoni wait()
po ponownym zaplanowaniu, nawet jeśli może zrobić postęp.
Synchronizując obiekt współdzielony, możesz upewnić się, że ten problem nie wystąpi, ponieważ take()
wywołanie drugiego wątku nie będzie w stanie robić postępu, dopóki pierwszy wątek nie zostanie faktycznie zablokowany.
Po drugie, musisz umieścić warunek sprawdzany w pętli while, a nie instrukcję if, ze względu na problem zwany fałszywymi pobudzeniami. W tym miejscu wątek oczekujący może być czasami ponownie aktywowany bez notify()
wywołania. Umieszczenie tej kontroli w pętli while zapewni, że jeśli wystąpi fałszywe budzenie, warunek zostanie ponownie sprawdzony, a wątek zadzwoni wait()
ponownie.
Jak wspomniano w niektórych innych odpowiedziach, Java 1.5 wprowadziła nową bibliotekę współbieżności (w java.util.concurrent
pakiecie), która została zaprojektowana w celu zapewnienia abstrakcyjnego poziomu wyższego niż mechanizm oczekiwania / powiadamiania. Korzystając z tych nowych funkcji, możesz przepisać oryginalny przykład w następujący sposób:
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();
}
}
}
Oczywiście, jeśli rzeczywiście potrzebujesz kolejki blokującej, powinieneś użyć implementacji interfejsu BlockingQueue .
Również w przypadku takich rzeczy bardzo polecam Java Concurrency in Practice , ponieważ obejmuje wszystko, co możesz chcieć wiedzieć o problemach i rozwiązaniach związanych z współbieżnością.
Nie przykład kolejki, ale niezwykle prosty :)
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();
}
}
}
Kilka ważnych punktów:
1) NIGDY nie rób
if(!pizzaArrived){
wait();
}
Zawsze używaj while (warunek), ponieważ
while(!pizzaExists){ wait(); }
.2) Musisz przytrzymać blokadę (zsynchronizowaną) przed wywołaniem funkcji wait / nofity. Wątki muszą również uzyskać blokadę przed przebudzeniem.
3) Staraj się unikać uzyskiwania blokady w synchronizowanym bloku i staraj się nie wywoływać metod obcych (metod, których nie wiesz na pewno, co robią). Jeśli musisz, koniecznie podejmij środki, aby uniknąć impasu.
4) Uważaj na powiadomienie (). Pozostań przy powiadomieniuAll (), dopóki nie dowiesz się, co robisz.
5) Na koniec przeczytajcie Współbieżność Java w praktyce !
pizzaArrived
flagi? jeśli flaga zostanie zmieniona bez wywołania, notify
nie będzie miała żadnego efektu. Również tylko z wait
i notify
wywoła przykład działa.
synchronized
słowo kluczowe, deklarowanie zmiennej jest zbędne volatile
i zaleca się unikanie jej, aby uniknąć pomyłek @mrida
Nawet jeśli poprosił wait()
a notify()
konkretnie, czuję, że ten cytat jest nadal ważne, wystarczy:
Josh Bloch, Effective Java 2nd Edition , pozycja 69: Preferuj narzędzia do współbieżności wait
i notify
(podkreślam jego):
Biorąc pod uwagę trudności związane z użyciem
wait
inotify
poprawnie, należy użyć narzędzi współbieżności wyższego poziomu zamiast [...] z użyciemwait
inotify
bezpośrednio jest jak programowanie w „asemblerze współbieżności” w stosunku do języka wyższego poziomu świadczonych przezjava.util.concurrent
. Rzadko, jeśli w ogóle, jest powód do używaniawait
inotify
nowego kodu .
notify()
i wait()
ponownie
Czy obejrzałeś samouczek Java ?
Ponadto radzę trzymać się z daleka od zabawy tego rodzaju rzeczami w prawdziwym oprogramowaniu. Dobrze się z tym bawić, żebyś wiedział, co to jest, ale współbieżność ma swoje pułapki. Lepiej jest stosować abstrakcje wyższego poziomu i zsynchronizowane kolekcje lub kolejki JMS, jeśli budujesz oprogramowanie dla innych osób.
Tak przynajmniej robię. Nie jestem ekspertem w dziedzinie współbieżności, więc w miarę możliwości unikam ręcznej obsługi wątków.
Przykład
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
}
}
Przykład funkcji wait () i replaceall () w wątku.
Zsynchronizowana statyczna lista tablic jest używana jako zasób i wywoływana jest metoda wait (), jeśli lista tablic jest pusta. Metoda powiadomienie () jest wywoływana po dodaniu elementu do listy tablic.
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)
, myślę, że może to być pomyłka.
notify
budzi tylko jeden wątek. Jeśli dwa wątki konsumenckie rywalizują o usunięcie elementu, jedno powiadomienie może obudzić drugi wątek konsumencki, co nie może nic z tym zrobić i wróci do trybu uśpienia (zamiast producenta, który, jak mieliśmy nadzieję, wstawi nowy element). Ponieważ wątek producenta nie został przebudzony, nic nie zostało wstawione, a teraz wszystkie trzy wątki będą spały w nieskończoność. Usunąłem mój poprzedni komentarz, ponieważ (niesłusznie) stwierdził, że przyczyną problemu był fałszywy