Interfejs Lock
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
Klasa ReentrantLock implementuje ten interfejs oferując wilowejściową blokadę podobną do blokady synchronized. Więc po co ten nowy interfejs? Ponieważ blokady synchronized mają swoje ograniczenia a zastąpieniem ich obiektem Lock pomaga je wyeliminować. Te ograniczenia to:
- blokada synchronized jest ograniczona tylko do jednego bloku, jej bloku - jest to także jej zaleta bo blokady synchronized się nie zwalnia a blokadę stworząną przy użyciu obiektu lock już trzeba zwolnić - zwykle w bloku finally
- wątku oczekującego na blokadzie nie można przerwać a interfejs lock ma metodę lockinterruptibly lub tryLock dzięki którym można stworzyć zadanie które mimo oczekiwania na blokadę będzie można anulować poprzez przerwanie (interrupt)
- blokada Lock ma możliwość pytania o blokadę i nie blokowania jeśli jej nie może uzyskać dzięki metodzie tryLock (bez parametrów) dzięki czemu można uzyskać blokadę bez blokowania wątku
- blokada Lock ma możliwość uzyskania blokady z limitem czasu dzięki czemu można pisać zadania z budżetem czasu na ich wykonanie - metoda tryLock z parametrami
- blokada instrinct lock umożliwia wywoływanie metod wait, notify i notifyAll ale nie są one powiązane z warunkami których może być wiele, dzięki blokadzie Lock można stworzyć wiele warunków dla jeden blokady i na warunkach wywoływać metody await, signal, signalAll (interfejs Condition i metoda newCondition)
- klasa ReentrantLock umożliwia stworzenie blokady która będzie sprawiedliwa (fair)
- w javie 5 obiekty Lock działały dużo szybciej niż blokady synchronized jednak poprawiono to i w javie 6 nie ma już różnic w wydajności
Lock lock = new ReentrantLock();
lock.lock();
try {
// access the resource protected by this lock
} finally {
lock.unlock();
}
Typowe użycie metody tryLock:
Lock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
} else {
// perform alternative actions
}
Sprawiedliwość
Tak jak wcześniej wspomniano możliwe jest stworzenie blokady która jest sprawiedliwa, domyślnie blokada lock jak i synchronized (instrinct lock) nie są sprawiedliwe. To znaczy że nie ma znaczenia który wątek w jakiej kolejności poprosił o dostęp do blokady. Statystycznie rzecz biorąc każdy w końcu otrzyma blokadę i nie ma tu zagrożenia żywotności a wybudzanie specjalnie wątku który jest pierwszy w kolejce może być - w praktyce jest - bardzo kosztowne. Także blokadę otrzymuje ten wątek który może ją otrzymać i użyć najszybciej - więc nie jest np uśpiony.
Sprawiedliwe blokady mogą się tylko przydać jeśli są trzymane bardzo długo.
Który sposób blokowania wybrać
Powinno się używać interfejsu lock tylko jeśli go potrzebujemy - czyli potrzebujemy jego zaawansowanych możliwości - w innym przypadku nie ma to sensu. Dużo prościej jest używać słowa kluczowego synchronized, nie trzeba także zwalniać blokad synchronized co sprawia że programowanie jest bezpieczniejsze. Blokady synchronized wcale nie są przestarzałym narzędziem cały czas są i będą używane.
Blokady do odczytu i zapisu: ReadWriteLock
Kolejnym sposobem na zwiększenie wydajności w dostępie do danych współdzielonych jest użycie blokad read write. Blokady te są użyteczne jeśli dominującą operacją na danych jest odczyt. W takim wypadku dostęp do czytania ma wiele wątków, a jeden wątek może pisać. Dane nie są w żaden sposób zagrożone i wątki zawsze widzą prawdziwą i ostatnią zapisaną wartość.
Implementajcją interfejsu ReadWriteLock jest klasa ReentrantReadWriteLock, ma ona możliwość określenia sprawiedliwości, jeśli blokada jest trzymana przez czytających i pojawia się wątek który chce pisać to więcej czytających nie dostaje dostępu zanim wątek zapisujący nie dostanie i zwolni blokady, możliwe jest zmiana blokady z zapisywania do czytania ale nie na odwrót.
Brak komentarzy:
Prześlij komentarz