Specific synchronization primitives #
In Java there are some possibilities for synchronization. The monitor lock with the synchronized
keyword is only one of them.
- Semaphore
- Lock & Condition
- Read-Write Lock
- CountDownLatch
- CyclicBarrier
- Exchanger
The semaphore #
The semaphore can be used in very many places, since other synchronization primitives are built on top of it.
The semaphore is initialized with either 0, 1 or any other arbitrary number. As long as the number is greater than 0, aquire(N)
can be used to decrease the number by N. If the number is 0, any thread that wants to take something out waits until someone has increased the number by N with release(N)
. The user can think of the semaphore as a pot of entries. Threads are allowed to continue if they have a ticket, and if they don’t, they wait until there is one in the pot.
The semaphore can be used for many things. If it always oscillates between 1 or 0, a resource like a variable or a service can be protected. For semaphores that oscillate between N and 0, N threads can use the resource at the same time (e.g. for quotas).
Lock & Condition #
ReentantLock #
If a resource is locked and waits for a certain condition, at best the ReentrantLock
class simplifies the code. For example, a condition on “nonFull” and on “nonEmpty” can be created. This can bring a substantial performance profit, because it can be waited now on “nonFull” or “nonEmpty” and it is not woken up unnecessarily many Threads.
private Lock monitor = new ReentrantLock(true);
private Condition nonFull = monitor.newCondition();
private Condition nonEmpty = monitor.newCondition();
ReentrantReadWriteLock #
Allowing only one thread to read or write a resource each time may be unnecessary. Normally, multiple threads should be allowed to read, but only one thread should be allowed to write. For this purpose there is the class 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 #
The CountDownLatch blocks as long as the counter is > 0. It is used to synchronize multiple threads in time. For example, it can be used to make all threads wait until all threads have reached the barrier.
CountDownLatch ready = new CountDownLatch(N);
\\ `` in the threads then respectively
ready.countDown();
ready.await();
Because countDown()
always reduces the counter by 1, the CountDownLatch can only be used once.
CyclicBarrier #
The CyclicBarrier works similar to the CountDownLatch, but can be used multiple times. It closes after all threads have passed.
CyclicBarrier start = new CyclicBarrier(N);
start.await();
Exchanger #
With the Exchanger
an object can be exchanged. The method V exchange(V x)
blocks until the other thread calls the same method. The return value is the object of the other thread.