Here is a Buffer:
Imaging that you have two producers threads calling 'put(int i)' and one consumer thread calling get().
Imaging the following sequence of events:
- Consumer calls get() and blocks waiting for lock
- Producer1 calls put(int i) and, while it is in the synchronized block, Producer2 starts and also waits for the lock currently held by Producer1. Therefore both the Consumer and Producer2 are waiting.
- Producer2 releases the lock and calls notifyAll()
- The JVM chooses to wake Producer2 first
- Producer2 enters the synchronised block, sees it cannot write and goes back to waiting.
- Then the Consumer gets the lock, enters the block in the get() method and retrieves the variable.
- etc
This is totally functional code but the problem is that the JVM wakes all the threads, both producers and consumers. This is fine in our example but imagine we had thousands of producers and consumers; suddenly it becomes very inefficient and memory intensive to wake up all the threads, when most of them will just go straight back to waiting because their predicate is not fulfilled.
The solution? Java 5 reentrant locks!
These allow you to have different wait conditions and wake only the threads that are waiting for that particular condition.
The modified solution is shown below:
Imaging that you have two producers threads calling 'put(int i)' and one consumer thread calling get().
This file contains 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
private int currentlyHeldValue = 0; | |
@Override | |
public void put(int i) throws InterruptedException { | |
synchronized(this){ | |
while (currentlyHeldValue != 0){ | |
wait(); | |
} | |
currentlyHeldValue = i; | |
notifyAll(); | |
} | |
} | |
@Override | |
public int get() throws InterruptedException { | |
synchronized(this){ | |
while (currentlyHeldValue == 0){ | |
wait(); | |
} | |
int valueToReturn = currentlyHeldValue; | |
currentlyHeldValue = 0; | |
notifyAll(); | |
return valueToReturn; | |
} | |
} |
- Consumer calls get() and blocks waiting for lock
- Producer1 calls put(int i) and, while it is in the synchronized block, Producer2 starts and also waits for the lock currently held by Producer1. Therefore both the Consumer and Producer2 are waiting.
- Producer2 releases the lock and calls notifyAll()
- The JVM chooses to wake Producer2 first
- Producer2 enters the synchronised block, sees it cannot write and goes back to waiting.
- Then the Consumer gets the lock, enters the block in the get() method and retrieves the variable.
- etc
This is totally functional code but the problem is that the JVM wakes all the threads, both producers and consumers. This is fine in our example but imagine we had thousands of producers and consumers; suddenly it becomes very inefficient and memory intensive to wake up all the threads, when most of them will just go straight back to waiting because their predicate is not fulfilled.
The solution? Java 5 reentrant locks!
These allow you to have different wait conditions and wake only the threads that are waiting for that particular condition.
The modified solution is shown below:
This file contains 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
private Lock lock = new ReentrantLock(); | |
private Condition valueAlreadySet = lock.newCondition(); | |
private Condition valueNotSetYet = lock.newCondition(); | |
private int currentlyHeldValue = 0; | |
@Override | |
public void put(int i) throws InterruptedException { | |
lock.lock(); | |
try { | |
while (currentlyHeldValue != 0){ | |
valueAlreadySet.await(); | |
} | |
currentlyHeldValue = i; | |
valueNotSetYet.signal(); | |
} | |
finally{ | |
lock.unlock(); | |
} | |
} | |
@Override | |
public int get() throws InterruptedException { | |
lock.lock(); | |
try { | |
while (currentlyHeldValue == 0){ | |
valueNotSetYet.await(); | |
} | |
int valueToReturn = currentlyHeldValue; | |
currentlyHeldValue = 0; | |
valueAlreadySet.signal(); | |
return valueToReturn; | |
} | |
finally { | |
lock.unlock(); | |
} | |
} |
No comments:
Post a Comment