Spezifische Synchronisationsprimitiven #
In Java gibt es einige Möglichkeiten zur Synchronisation. Den Monitor-Lock mit dem synchronized
keyword ist nur eine davon.
- Semaphor
- Lock & Condition
- Read-Write Lock
- CountDownLatch
- CyclicBarrier
- Exchanger
Der Semaphor #
Den Semaphor kann an sehr vielen Orten verwendet werden, da andere Synchronisation-Primitiven darauf aufbauen.
Der Semaphor wird entweder mit 0, 1 oder einer anderen beliebigen Zahl initialisiert. Solange die Zahl grösser als 0 ist, kann mit aquire(N)
die Zahl um N reduziert werden. Wenn die Zahl 0 ist, wartet jeder Thread, der etwas raus nehmen will, bis jemand mit release(N)
die Zahl um N erhöht hat. Der Semaphor kann sich der Benutzer wie ein Topf mit Eintritten vorstellen. Es dürfen Threads weitergehen, wenn sie ein Ticket haben und wenn sie keines haben, warten sie bis es eines im Topf hat.
Der Semaphor kann für viele Dinge benutzt werden. Wenn er immer zwischen 1 oder 0 pendelt, kann eine Ressource wie eine Variable oder ein Service geschützt werden. Bei Semaphoren, die zwischen N und 0 pendeln, können N Threads die Ressource gleichzeitig verwenden (z.B. für Quotas).
Lock & Condition #
ReentantLock #
Wenn eine Ressource gesperrt ist und auf eine bestimmte Bedingung wartet, vereinfacht allenfalls die ReentrantLock
Klasse den Code. Es kann dann so beispielsweise eine Condition auf “nonFull” und auf “nonEmpty” erstellt werden. Das kann einen erheblichen Performance Gewinn bringen, denn es kann nun auf “nonFull” oder “nonEmpty” gewartet werden und es werden nicht unnötig viele Threads geweckt.
private Lock monitor = new ReentrantLock(true);
private Condition nonFull = monitor.newCondition();
private Condition nonEmpty = monitor.newCondition();
ReentrantReadWriteLock #
Jedes Mal nur einem Thread eine Ressource für das Lesen oder das Schreiben zu überlassen, kann unnötig sein. Normalerweise sollten mehrere Threads lesen dürfen aber nur ein Thread schreiben. Dafür gibt es die Klasse ReentrantReadWriteLock
ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
rwLock.readLock().lock();
// read-only accesses
rwLock.readLock().unlock();
rwLock.writeLock().lock();
// write (and read) accesses
rwLock.writeLock().unlock();
CountDownLatch #
Die CountDownLatch blockiert solange der Zähler > 0 ist. Sie dient der zeitlichen Synchronisation von mehreren Threads. Man kann damit zum Beispiel alle Threads warten lassen, solange noch nicht alle Threads die Barriere erreicht haben.
CountDownLatch ready = new CountDownLatch(N);
\\ in den Threads dann jeweils
ready.countDown();
ready.await();
Dadurch, dass mit countDown() der Zähler immer um 1 reduziert wird, kann die CountDownLatch nur einmal verwendt werden.
CyclicBarrier #
Die CyclicBarrier funktioniert ähnlich wie die CountDownLatch, kann aber mehrfach verwendet werden. Sie schliesst nachdem alle Threads durchgegangen sind.
CyclicBarrier start = new CyclicBarrier(N);
start.await();
Exchanger #
Mit dem Exchanger
kann ein Object getauscht werden. Die Methode V exchange(V x)
blockiert solange, bis der andere Thread die selbe Methode aufruft. Der Rückgabewert ist jeweils das Object des anderen Threads.