Last active
March 15, 2020 15:20
-
-
Save pedrominicz/1d976280c13e2900fa16aa5c7f2dfebf to your computer and use it in GitHub Desktop.
Example using semaphores in Java.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.concurrent.Semaphore; | |
// This program illustrates the use of semaphores to synchronize access to a | |
// buffer shared between multiple threads. Semaphores are a weird | |
// synchronization define and I believe they can be best understood by example. | |
// | |
// A semaphore is an atomic counter offering two methods: `acquire` and | |
// `release`. `acquire` decreases the counter if it is greater than zero, | |
// otherwise it blocks the current thread until the counter increases and it | |
// can do so. `release` increases the counter. | |
// | |
// It is simple to see that a semaphore can be used to implement a mutex lock. | |
// Initialize it to 1, each thread should `acquire` and subsequently `release` | |
// the mutex exactly once every time. Every time the semaphore is acquired its | |
// counter immediately becomes zero, ensuring only one thread at the time can | |
// `acquire` it. | |
// | |
// This mutex-like behaviour is used in this example. | |
class Buffer { | |
private static final int SIZE = 5; | |
private final Object[] buffer = new Object[SIZE]; | |
private int in = 0; | |
private int out = 0; | |
private final Semaphore mutex = new Semaphore(1); | |
// This semaphore counts how many empty slots exist in the buffer at any | |
// given time. | |
private final Semaphore empty = new Semaphore(SIZE); | |
// This semaphore counts how many non-empty slots exist in the buffer at | |
// any given time. | |
private final Semaphore full = new Semaphore(0); | |
// The `insert` and `remove` methods execute in a mutually exclisive manner | |
// due to the `mutex` semaphore. | |
public void insert(final Object object) throws InterruptedException { | |
// Decrease the empty slot counter. Note that the order of the | |
// `acquire`s is significant to avoid a deadlock. | |
empty.acquire(); | |
mutex.acquire(); | |
buffer[in] = object; | |
in = (in + 1) % SIZE; | |
mutex.release(); | |
// Increase the full slot counter. | |
full.release(); | |
} | |
public Object remove() throws InterruptedException { | |
// Decrease the full slot counter. | |
full.acquire(); | |
mutex.acquire(); | |
final Object object = buffer[out]; | |
out = (out + 1) % SIZE; | |
mutex.release(); | |
// Increase the empty slot counter. | |
empty.release(); | |
return object; | |
} | |
} | |
public class Main { | |
public static void main(String[] args) { | |
final Buffer buffer = new Buffer(); | |
// Producer thread. The only noticeable curiosity about it is that is | |
// makes use of anonymous classes. | |
new Thread(new Runnable() { | |
@Override | |
public void run() { | |
for (int i = 0; true; ++i) { | |
try { | |
// `insert` may throw `InterruptedException` which must | |
// be caught. | |
// | |
// Note that the produces is way faster than the | |
// consumer because the latter calls `sleep`. However, | |
// the `insert` method blocks if the buffer is full. | |
buffer.insert(new Integer(i)); | |
} catch (InterruptedException ie) { | |
// Empty. | |
} | |
} | |
} | |
}).start(); | |
new Thread(new Runnable() { | |
@Override | |
public void run() { | |
while (true) { | |
try { | |
// Both `remove` and `sleep` may throw | |
// `InterruptedException` which must be caught. | |
System.out.println((Integer) buffer.remove()); | |
Thread.sleep(1000); | |
} catch (InterruptedException ie) { | |
// Empty. | |
} | |
} | |
} | |
}).start(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment