Metody zależne od stanu klas
Struktura takiej operacji może wyglądać tak
1. Uzyskać blokadę na stanie
2. Dopóki (warunek nie jest spełniony)
2.1. Zwolnij blokadę
2.2. Poczekaj aż warunek może być spełniony
2.3. Opcjonalnie wyrzuć wyjątek jeśli jeśli wystąpi przerwanie albo upłynął limit czasu
2.4. Uzyskaj blokadę i wróć do punktu 2
3. Wykonaj operację
4. Zwolniej blokadę
Do takich operacji służą kolejki warunkowe (condition queues) - metody wait, notify, notifyAll jeśli korzystamy z zwykłych blokad instrinct lock - za pomocą słowa kluczowego synchronized.
Przykład stosu z ograniczoną wielkością (dość toporny ale działający):
public class BoundedStack<N> {
private Node<N> top;
private int maxElements;
private int count = 0;
public BoundedStack(int maxElements) {
this.maxElements = maxElements;
}
private static class Node<N> {
private final N element;
private final Node<N> next;
public Node(N element, Node<N> next) {
super();
this.element = element;
this.next = next;
}
}
public synchronized void push(N element) throws InterruptedException {
while (isFull()) {
wait();
}
top = new Node<>(element, top);
count++;
notifyAll();
}
public synchronized N pop() throws InterruptedException {
while (isEmpty()) {
wait();
}
N result = top.element;
top = top.next;
count--;
notifyAll();
return result;
}
private synchronized boolean isEmpty() {
return top == null;
}
private synchronized boolean isFull() {
return count == maxElements;
}
}
Kolejki warunkowe dla bloakad synchronized mogą być powiązane z wieloma warunami - jak w przykładowym ograniczonym stosie - mamy dwa warunki - isFull, isEmpty.
Metoda wait() zwalnia blokadę i usypia wątek po jego przebudzeniu wątek musi znowu ją uzyskać, nie jest w żaden sposób uprzywilejowany w stosunku do innych wątków próbujących uzyskać daną blokadę. Po uzyskaniu blokady należy po raz kolejny sprawdzić warunek ponieważ nie musi on być spełniony - dlatego należy metodę wait wywołać w pętli z niespełnionym warunkiem.
Należy nie zapomnieć o notyfikacji innych wątków po tym jak stan się zmieni - jeśli tego nie zrobimy wątki nigdy nie wybudzą się z metod wait. Do notyfikacji służą metody notify i notifyAll.
Można korzystać z metody notify jeśli istnieje tylko jeden warunek i każdy wątek wykonuje taką samą logikę po wyjściu z metody wait. Dodatkowo tylko jeden wątek ma prawo wykonywać pracę po wybudzeniu z notyfikacji. Jęśli nie jesteśmy tego pewni powinniśmy korzystać z metody notifyAll ponieważ jest ona bezpieczniejsza i gwarantuje że choć jeden wątek wykona progres. Jednak należy pamiętać że każdy z wybudzonych wątków będzie próbował uzyskać blokadę aby na nowo sprawdzić warunek - może to powodować dużo przełączeń kontekstu ale jeśli nie program działa dość szybko to nie należy się tym przejmować.
Metoda wait występuje w 3 odmianach -bezparametrowo i z limitem czasu jest także wrażliwa na przerwania.
Obiekty warunków - praca z obiektami Lock
Na przykład nasza zmieniona klasa stosu może mieć postać:
public class BoundedStackWithLock<N> {
private Node<N> top;
private int maxElements;
private int count = 0;
private Lock stackLock = new ReentrantLock();
private Condition isEmptyCondition = stackLock.newCondition();
private Condition isFullCondition = stackLock.newCondition();
public BoundedStackWithLock(int maxElements) {
this.maxElements = maxElements;
}
private static class Node<N> {
private final N element;
private final Node<N> next;
public Node(N element, Node<N> next) {
super();
this.element = element;
this.next = next;
}
}
public void push(N element) throws InterruptedException {
stackLock.lock();
while (isFull()) {
isFullCondition.await();
}
top = new Node<>(element, top);
count++;
isEmptyCondition.signal();
stackLock.unlock();
}
public N pop() throws InterruptedException {
stackLock.lock();
while (isEmpty()) {
isEmptyCondition.await();
}
N result = top.element;
top = top.next;
count--;
isFullCondition.signal();
stackLock.unlock();
return result;
}
private boolean isEmpty() {
return top == null;
}
private boolean isFull() {
return count == maxElements;
}
}
Należy pamiętać aby przez pomyłkę nie wywołać metod wait, notify, notifyAll na obiektach condition. Ich odpowiedniki to await, signal, signalAll. Jest także dodatkowa metoda awaitUninterruptibly która jak sama nazwa wskazuje nie jest wrażliwa na przerwania.
Brak komentarzy:
Prześlij komentarz