Doubly Check Lock Dosen't work on this java code? - java

I am trying to fix a service by optimising the synchronisation blocks. I am getting two different values and my doubly check singleton with a volatile string dosen't seem to work?
The get and Increment string puts a row lock on DB and increments the string so the unique update is taken care at DB level only. So in the first case, no issues.
Problem lies in the else blozk. When correlation ID is not null, then we try and fetch an already mapped value, if this is the first call. then we first map the value and then return it. This mapping has to be synchronised so that two different threads don't update next val agter both of them find it to be null.
This class is also a singleton service.
public class RangeQueryService{
private volatile String nextValue=null;
public String getNextIncrement(String name, String correlationId) throws SomeCheckedException {
try {
if (correlationId == null) {
nextValue = rangeFetch.getAndIncrementAsString(name);
} else { //Enter Sync branch
// mapper Will Return null if no value is mapped.
nextValue = mapper.mapToB(SOME_CONST, correlationId);
// Avoid syncronization overhead if value is already fetched. Only enter if nextVal is null.
if (nextValue == null) {
synchronized (this) {
Doubly Check lock pattern, as two threads can find null simultaneously, and wait on the critical section.
if(nextValue==null){
nextValue = rangeFetch.getAndIncrementAsString(name);
idMapper.mapToB(SOME_CONST, correlationId, nextValue, DURATION);
}
}
}
}
return nextValue;
} catch (Exception e) {
throw new SomeCheckedException("Error!" + e.getMessage());
}
}
It Return both 19 and 20. it should only return 19.
Output:
headerAfterProcessOne: 0000000019, headerAfterProcessTwo: 0000000020

If I understood you in the right way, you expect one thread (lets call it A) to wait for another (which increments the value to 19, B), and then skip the incrementing because nextValue is 19 and not null. But the change is unseen by the waiting thread.
The possible scenario is much more complicated, as I see:
The problem is that 19 returned by the A thread, the one which waits, as it immediately skips the whole block after the volatile value is posted by the B thread at line:
nextValue = rangeFetch.getAndIncrementAsString(name);
Another case, the A thread enters the method and nextValue is already posted.
So it immediately jumps to return statement and return 19, which was set by the B thread (Yes, it is unexpected, but yet this happens sometimes). You should not expect the A thread to wait for the B to finish executing. The B (which reached synchronized block first) finish processing (incrementing) the value and returns 20.
There are possible workarounds for that, though:
if (nextValue == null) {
synchronized(this) {
if(nextValue == null) {
String localTemp = rangeFetch.getAndIncrementAsString(name);
idMapper.mapToB(SOME_CONST, correlationId, localTemp, DURATION);
nextValue = localTemp;
}
}
}
The overall point is that changes made to nextValue immediately affect another calls of getNextIncrement.
It is really hard to debug problems in this pattern, so I may be wrong, but I post an answer anyway, since my explanation is too long for the comment.

Related

Concurrency Issue with HashMap and ReentrantLock

I have a piece of code that on startup creates a HashMap of key to ReentrantLock.
void constructor() {
this.lockMap = new HashMap<>();
for (int i=0; i<100; i++) {
this.lockMap.put(i, new ReentrantLock(true));
}
}
During concurrent execution, I try to lock the lock inside the lockMap in the following manner:
runConcurrently() {
ii = 10;
if (!lockMap.containsKey(ii)) {
log.error("lock id is not found in the lockMap " + ii);
}
locked = lockMap.get(ii).tryLock();
if (!locked) {
return;
}
runCriticialSection();
lockMap.get(ii).unlock();
}
void runCriticialSection() {
log.info("hello");
log.info("I'm here");
}
so here is what I have seen happen once in while every 4 hours the code is running, in a very rare occurrence.
I see these logs:
hello.
hello.
I'm here.
I'm here.
and then I see this log right after on third time accessing the hasmap on the same key ii =10:
lock id is not found in the map 10.
NullPointerExeception ... trying to access the map.
where I should see in guaranteed ordering:
hello.
I'm here.
hello.
I'm here.
The Hashmap never gets modified during execution at all.
is there an issue with hashmap not being concurrent hashmap? is get, not threadsafe in absence of modifications? I am specifically not using it due to locking slowness in concurrent hasmap. But the hashmap is only created on startup and never modified after. I find it very weird where it seems the lock has been acquired twice and it seems like the element is missing from the map.
There is no concurrency issue with the map itself, if the map is never modified after the constructor. If so, threads will only ever see that final version of the map. Else, the behaviour is undefined.
No exclusive access of the critical section
From your output, it appears that (at least) two threads accessed runCriticialSection simultaneously.
This is due to the fact that you are using a different lock for each value of ii. A lock only excludes another thread from locking it, if that other threads uses that same lock! Thus, threads that do not use the same value of ii, will effortlessly run runCriticialSection simultaneously. That can result in the described output anomaly as shown above, as follows:
Thread 1 executes log.info("hello");
Thread 2 executes log.info("hello");
Thread 1 executes log.info("I'm here");
Thread 2 executes log.info("I'm here");
If you want exclusive access to a section, always use the same lock surrounding that section.
Coding problems
When the check fails that ii maps to a lock, you should not continue but instead return or throw an exception. If you don't, locked = lockMap.get(ii).tryLock(); throws a NullPointerExcetpion, because lockMap.get(ii) returns null.
Between locking the lock and unlocking it, you are running user code, in the form of runCriticalSection. If you change the implementation of that method later and it starts throwing things: your lock will never unlock! Always use try ... finally with a lock.
Fixing these issues, could lead to the following code:
if (!lockMap.containsKey(ii)) {
log.error("lock id is not found in the lockMap " + ii);
return;
}
locked = lockMap.get(ii).tryLock();
if (!locked) {
return;
}
try {
runCriticialSection();
}
finally {
lockMap.get(ii).unlock();
}
Actually, I would just put the lock in a local variable, but that is a matter of opinion.
ReentrantLock lock = lockMap.get(ii);
if (lock == null) {
log.error("lock id is not found in the lockMap " + ii);
return;
}
locked = lock.tryLock();
if (!locked) {
return;
}
try {
runCriticialSection();
}
finally {
lock.unlock();
}

Reducing the scope of a synchronized block in Java unexpectedly corrupts my ArrayList, why is that the case?

A bit late, I have a Christmas special for you. There is a Santa class with an ArrayList of presents and a Map to keep track which children already have got their presents. Children modeled as threads constantly asking Santa for presents at the same time. For simplicity, each child receives exactly one (random) present.
Here is the method in the Santa class occasionally yielding a IllegalArgumentException because presents.size() is negative.
public Present givePresent(Child child) {
if(gotPresent.containsKey(child) && !gotPresent.get(child)) {
synchronized(this) {
gotPresent.put(child, true);
Random random = new Random();
int randomIndex = random.nextInt(presents.size());
Present present = presents.get(randomIndex);
presents.remove(present);
return present;
}
}
return null;
}
However, making the whole method synchronized works just fine. I don't really understand the problem with the smaller sized synchronized block shown before. From my point of view, it should still assure that a present isn't assigned to a kid multiple times and there shouldn't be concurrent writes (and also reads) on the presents ArrayList. Could you please tell me why my assumption is wrong?
That happens because the code contains a race condition. Let us use the following example to illustrate that race condition.
Imagine that Thread 1 reads
`if(gotPresent.containsKey(child) && !gotPresent.get(child))`
and it evaluates as true. While Thread 1 enters the synchronized block, another thread (i.e., Thread 2) also reads
if(gotPresent.containsKey(child) && !gotPresent.get(child))
before Thread 1 has had the time to do gotPresent.put(child, true);. Consequently, the aforementioned if also evaluates as true for Thread 2.
Thread 1 is inside the synchronized(this) and removes the present from the list of presents (i.e., presents.remove(present);). Now the size of the present list is 0. Thread 1 exits the synchronized block, while Thread 2 just enters it, and eventually calls
int randomIndex = random.nextInt(presents.size());
since presents.size() will return 0, and the random.nextInt implementation is as follows:
public int nextInt(int bound) {
if (bound <= 0)
throw new IllegalArgumentException(BadBound);
...
}
you get the IllegalArgumentException exception.
However, making the whole method synchronized works just fine.
Yes, because with
synchronized(this) {
if(gotPresent.containsKey(child) && !gotPresent.get(child)) {
gotPresent.put(child, true);
Random random = new Random();
int randomIndex = random.nextInt(presents.size());
Present present = presents.get(randomIndex);
presents.remove(present);
return present;
}
}
in the aforementioned race-condition example Thread 2 would have been waiting before the
if(gotPresent.containsKey(child) && !gotPresent.get(child))
and because Thread 1, before exiting the synchronized block, would have done
gotPresent.put(child, true);
by the time Thread 2 would have entered the synchronized block the following statement
!gotPresent.get(child)
would have evaluated as false, and consequently Thread 2 would have exit immediately without calling int randomIndex = random.nextInt(presents.size()); with a list of size 0.
Since the method that you have shown is being executed in parallel by multiple threads you should ensure mutual exclusion of the shared data structure among threads, namely gotPresent and presents. Which implies, for instance, that operations like containsKey, get, and put should be performed within the same synchronized block.

Use a semaphore in writer reader

So I'm attending a course in multi threaded development and are currently learning about semaphores. In our latest assignment we are supposed to use three threads and two queues. The writer thread will write chars to the first queue, then a "encryptor" thread will read the chars from that queue, encrypt the char and then add it to the second queue. Then we have a reader thread which reads from the second queue. To handle synchronization we are supposed to use semaphore's and mutex, but I managed without any:
public class Buffer {
private Queue<Character> qPlain = new LinkedList<Character>();
private Queue<Character> qEncrypt = new LinkedList<Character>();
private final int CAPACITY = 3;
public Buffer() {
System.out.println("New Buffer!");
}
public synchronized void addPlain(char c) {
while (qPlain.size() == CAPACITY) {
try {
wait();
System.out.println("addPlain is waiting to add Data");
} catch (InterruptedException e) {
}
}
qPlain.add(c);
notifyAll();
System.out.println("addPlain Adding Data-" + c);
}
public synchronized char removePlain() {
while (qPlain.size() == 0) {
try {
wait();
System.out.println("----------removePlain is waiting to return Data.");
} catch (InterruptedException e) {
}
}
notifyAll();
char c = qPlain.remove();
System.out.println("---------------removePlain Returning Data-" + c);
return c;
}
public synchronized void addEncrypt(char c) {
while (qEncrypt.size() == CAPACITY) {
try {
wait();
System.out.println("addEncrypt is waiting to add Data");
} catch (InterruptedException e) {
}
}
qEncrypt.add(c);
notifyAll();
System.out.println("addEncrypt Adding Data-" + c);
}
public synchronized char removeEncrypt() {
while (qEncrypt.size() == 0) {
try {
wait();
System.out.println("----------------removeEncrypt is waiting to return Data.");
} catch (InterruptedException e) {
}
}
notifyAll();
char c = qEncrypt.remove();
System.out.println("--------------removeEncrypt Returning Data-" + c);
return c;
}
}
So this works fine, but I'm not going to pass as I haven't used any semaphore. I do understand the concept, but I just don't see the point to use any in this case. I have 2 queues and just one reader and writer for each one.
EDIT: Updated with Semaphores instead. It almost works, problem arises when the removePlain() method get's called when the queue is empty. I'm pretty sure I should block it, but I'm lost here. Could I not just use a mutex here instead?
public class Buffer {
private Semaphore encryptedSem = new Semaphore(0);
private Semaphore decryptedSem = new Semaphore(0);
private final Queue<Character> qPlain = new LinkedList<Character>();
private final Queue<Character> qEncrypt = new LinkedList<Character>();
private final int CAPACITY = 3;
private boolean startedWrite = false;
private boolean startedRead = false;
/**
* Adds a character to the queue containing non encrypted chars.
*
* #param c
*/
public void addPlain(char c) {
// Makes sure that this writer executes first.
if (!startedWrite) {
startedWrite = true;
encryptedSem = new Semaphore(1);
}
if (qPlain.size() < CAPACITY) {
aquireLock(encryptedSem);
System.out.println("addPlain has lock");
qPlain.add(c);
realeseLock(encryptedSem);
}
}
/**
* Removes and returns the next char in the non encrypted queue.
*
* #return
*/
public char removePlain() {
// TODO Need to fix what happens when the queue is 0. Right now it just
// returns a char that is 0. This needs to be blocked somehow.
char c = 0;
if (qPlain.size() > 0) {
aquireLock(encryptedSem);
System.out.println("removePlain has lock");
c = qPlain.remove();
realeseLock(encryptedSem);
} else {
System.out.println("REMOVEPLAIN CALLED WHEN qPlain IS EMPTY");
}
return c;
}
/**
* Adds a character to the queue containing the encrypted chars.
*
* #param c
*/
public void addEncrypt(char c) {
if (!startedRead) {
startedRead = true;
decryptedSem = new Semaphore(1);
}
if (qEncrypt.size() < CAPACITY) {
aquireLock(decryptedSem);
System.out.println("addEncrypt has lock");
qEncrypt.add(c);
realeseLock(decryptedSem);
}
}
/**
* Removes and returns the next char in the encrypted queue.
*
* #return
*/
public char removeEncrypt() {
char c = 0;
if (qEncrypt.size() > 0) {
aquireLock(decryptedSem);
System.out.println("removeEncrypt has lock");
c = qEncrypt.remove();
realeseLock(decryptedSem);
}
return c;
}
/**
* Aquries lock on the given semaphore.
*
* #param sem
*/
private void aquireLock(Semaphore sem) {
try {
sem.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Realeses lock on the given semaphore.
*
* #param sem
*/
private void realeseLock(Semaphore sem) {
sem.release();
}
}
OK, so trying to adress your concerns, without doing your homework :-)
About your first sample
At first sight, this is a working sample. You are using a form of mutual exclusion through the synchronized keyword, which allows you to use this.wait/notify correctly. This also provides safeguards seeing every thread synchronizes on the same monitor, which provides adequate happen-before safety.
In other words, thanks to this single monitor, you are assured that anything under the synchronized methods is executed exclusively and that these methods side-effects are visible inside the other methods.
Only minor gripe is that your queues are not final, which according to safe object publication guidelines and depending on how your whole system/threads is bootstraped, might lead to visibility issues. Rule of thumb in multithreaded code (and maybe even generally) : whatever can be made final should be.
The real problem with your code is that it does not fulfill your requirements : use semaphores.
About your second sample
Unsafe boolean mutation
This one has real issues. First, your startedWrite/ startedRead booleans : you mutate them (change their true/false value) outside of any synchronization (lock, semaphores, syncrhonized, ... nothing at all). This is unsafe, under the java memory model it would be legal for a thread that has not performed the mutation to not see the mutated value. Put it another way, the first write could set startedWrite to true, and it could be that all other threads never see that true value.
Some discussions on this :
- https://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html
- Java's happens-before and synchronization
So anything that relies on these booleans is inherently flawed in your sample. That is, your Semaphore assignments, for one thing.
Several ways to correct this :
Always mutate shared state under a synchonization tool of some sort (in your first sample, it was the synchronized keyword, and here it could be your semaphores), and make sure that the same tool is used by all threads mutating or accessing the variable
Or use a concurrently safe type, like AtomicBoolean is this case, which has concurrency guarantees that any mutation is made visible to other threads
Race conditions
Another issue with your second code sample, is that you check the sizes of your queues before taking a lock and modifiying them, that is :
if (qPlain.size() > 0) {
aquireLock(encryptedSem);
...
c = qPlain.remove();
realeseLock(encryptedSem);
} else {
System.out.println("REMOVEPLAIN CALLED WHEN qPlain IS EMPTY");
}
Two concurrent threads could perform the check at the first line at the same time, and behave wrongly. Typical scenario is :
qplain has a size of 1
Thread 1 arrives at the if, checks that qplain is not empty, the check succeeds, then thread 1 is paused by the OS scheduler right here and now
Thread 2 arrives at the same if and the same check succeeds for the same reason
Thread 1 and Thread 2 resume from there on, both think they are allowed to take 1 element out of qplain which is wrong, because qplain has a size of 1 actually.
It will fail. You should have had a mutual exclusion of some sort. You can not (rule of thumb again) perform a check and after it perform a mutation under a lock. Both the check and the mutation should happen in, broadly speaking, the same lock. (Or you are a very advanced multithreading kind of guy and you know optimistic locking and stuf like that really well).
Possible deadlock
Another rule of thumb: any time you acquire and release a lock and/or a resource at the same call site, you should have a try/finally pattern.
That is, no matter how it is done, your code should always look like
acuquireSemaphore();
try {
// do something
} finally {
releaseSemaphore();
}
Same goes for locks, input or output streams, sockets, ... Failure to do so may lead to your semaphore being acquired but never released, especially in case of an uncaught exception. So do use try and finally around your resources.
Conclusions
With such serious flaws, I did not really read your code to see if the "spirit" of it works. Maybe it does, but at this point, it's not worth it to check it out further.
Going forward with your assignment
You are asked to use two tools : Semaphores and mutual exclusion (e.g. synchonized, or Lock I guess). Both are not exactly the same thing!
You probablye get mutual exclusions, as your first sample showed. Probably not Semaphores yet. The point of semaphores, is that they (safely) manage a number of "permits". One (a thread) can ask for a permit, and if the semaphore has one available and grants it, one can proceed with one's work. Otherwise, one is put in a "holding pattern" (a wait) untill a permit is available. At some point, one* is expected to give the permit back to the Semaphore, for others to use.
(*Please note : it is not mandatory for a semaphore to work that threads performing permit acquisition are the one to perform permit release. It is part of what make a lock and a semaphore so different, and it's a good thing).
Let's start simple : a Semaphore that only has one permit can be used as a mutual exclusion. Bonus point : it can be released by another thread than the one that acquired it. That makes it ideal for message passing between threads : they can exchange permits.
What does it remember us of ? Of wait / notify of course!
A possible path to a solution
So we have a semaphore, and it has a number of permits. What could the meaning of this number be ? A natural candidate is : have a Semaphore hold the number of elements inside the queues. At first, this could be zero.
Each time somebody puts an element in the queue, it raises the number of permits by one.
Each time somebody takes an element off the queue, it lowers the number of permits.
Then : trying to take an element off an empty queue means trying to acquire a permit from an empty semaphore, it will automatically block the caller. This seems to be what you want.
But!
We're yet to have a definition for "putting an element on top of a full queue". That is because semaphores are not bounded in permits. One can start with an empty semaphore and call "release" a thousand times, and end up with a 1000 permits available. We wil blow our maximal capacity without any kind of bounds.
Let's say we have a workaround for that, we're still not done : we did not make sure at this point that readers and writers do not modify the queue at the same time. And this is crucial for correctneess !
So we need other ideas.
Well issue #2 is easy : we are allowed to use exclusive locks for this exercie, so we'll use them. Just make sure that any manipulation to the list itself is under a synchonized block using the same monitor.
Issue number one... Well, we have a Semaphore representing a "not empty" condition. That's one of the two pairs of wait/notify you had in your first sample. OK cool, let's make another Semaphore representing a "not full" condition, the other wait/notifyPair of your code sample !
So recap : use a semaphore for each couple of wait/notify in your original sample. Keep a mutual exclusion to actually modify the contents of the queue object. And be very carreful of the articulation of the mutual exclusion part with the semaphores, it is the crux of the matter.
And I'll stop there to let you walk down this path if you want.
Bonus point
You should not have to code the same thing twice here. In your samples, you coded twice the same logic (one for the "clear text", once for the "encrypted"), basically, wait for (at least) a spot before stacking a char, and wait for the presence of (at least) a char before popping a it.
This should be one and the same code / methods. Do it once, and you'll get it right (or wrong of course) at all times. Write it twice, you double the chance of mistakes.
Future thoughts
This is all still very complex, for something that could be done using a `BlockingQueuè but then again, homeworks do have another purpose :-).
A bit more complex, but this message passing pattern of signaling having a thread waiting for a "notEmpty" signal, while the other waits on a "notFull" signal is the exact use case of the JDK Condition object, which mimicks the use of wait/notify.

Java ConcurrentHashMap actions atomicity

This may be a duplicate question, but I've found this part of code in a book about concurrency. This is said to be thread-safe:
ConcurrentHashMap<String, Integer> counts = new ...;
private void countThing(String thing) {
while (true) {
Integer currentCount = counts.get(thing);
if (currentCount == null) {
if (counts.putIfAbsent(thing, 1) == null)
break;
} else if (counts.replace(thing, currentCount, currentCount + 1)) {
break;
}
}
}
From my (concurrency beginners') point of view, thread t1 and thread t2 could both read currentCount = 1. Then both threads could change the maps' value to 2. Can someone please explain me if the code is okay or not?
The trick is that replace(K key, V oldValue, V newValue) provides the atomicity for you. From the docs (emphasis mine):
Replaces the entry for a key only if currently mapped to a given value. ... the action is performed atomically.
The key word is "atomically." Within replace, the "check if the old value is what we expect, and only if it is, replace it" happens as a single chunk of work, with no other threads able to interleave with it. It's up to the implementation to do whatever synchronization it needs to make sure that it provides this atomicity.
So, it can't be that both threads see currentAction == 1 from within the replace function. One of them will see it as 1, and thus its invocation to replace will return true. The other will see it as 2 (because of the first call), and thus return false — and loop back to try again, this time with the new value of currentAction == 2.
Of course, it could be that a third thread has updated currentAction to 3 in the meanwhile, in which case that second thread will just keep trying until it's lucky enough to not have anyone jump ahead of it.
Can someone please explain me if the code is okay or not?
In addition to yshavit's answer, you can avoid writing your own loop by using compute which was added in Java 8.
ConcurrentMap<String, Integer> counts = new ...;
private void countThing(String thing) {
counts.compute(thing, (k, prev) -> prev == null ? 1 : 1 + prev);
}
With put you can too replace the value.
if (currentCount == null) {
counts.put(thing, 2);
}

How to implement synchronized checks for Bounded Buffer to avoid Race Conditions?

Working with the classic multiple Consumer/Producer problem, and I have an issue that is driving me around the bend, regarding how to avoid race conditions when inserting/removing from a circular buffer. Appreciate any help in advance!
Sample code for circular buffer for example purposes. Similar to my implementation (Note: I cannot use collection types, only arrays for this):
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BoundedBuffer {
private final String[] buffer;
private final int capacity;
private int front;
private int rear;
private int count;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public BoundedBuffer(int capacity) {
super();
this.capacity = capacity;
buffer = new String[capacity];
}
public void deposit(String data) throws InterruptedException {
lock.lock();
try {
while (count == capacity) {
notFull.await();
}
buffer[rear] = data;
rear = (rear + 1) % capacity;
count++;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public String fetch() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
String result = buffer[front];
front = (front + 1) % capacity;
count--;
notFull.signal();
return result;
} finally {
lock.unlock();
}
}
}
What I need to know is how can I implement a method for checking if the buffer is full/Empty? This method needs to be included in this BoundedBuffer and must be called from another class (Producer/Consumer) before giving the go ahead for/Calling Inserting/Writing methods.
Pseudocode for method in Producer class.
if (!bufferFull) {
buffer.addelement;
}
else {
thread.sleep(5)
threadHasSleptFor++;
}
I am using threads, and there are multiple producers/consumers (In this case 2 producers/consumers, but I may require more). I need it so that if the buffer is full, the thread has to wait until it becomes available for insertion, and the time it waits needs to be stored for output purposes (Not debug, part of the core features). The issue I am having is this:
Thread 1 Producer checks is bufferfull condition, it's false.
Scheduler switches to Thread 2 midway.
Thread 2 also checks bufferfull condition, it's false.
thread 2 proceeds to insert.
Scheduler switches back to Thread 1.
Thread 1 now goes to insert line, as it already checked, but Thread 2 beat it.
Booom.
Somewhat new to Java, though as I understand this is the "time-of-check/time-of-use" race condition issue.
Can someone please advise as to how this can be implemented safely, and how would I loop the code so the threadHasSleptFor variable keeps incrementing on every fail (Providing the methods would be great). I want it so that only the Thread that has requested the check can begin to insert item; the second producer must wait for the lock.
Thanks.
This is by definition impossible to do without higher level locking.
You have to guarantee that the check of whether the buffer is full or not and the following insert are atomic from the thread's perspective which means you have to acquire some common lock to do so. This general problem is indeed called Time of check to time to use and leads to many interesting race conditions down the line.
The solution to these problems is to not check if you can do an operation and then do it, but to just try the operation and handle the error case. So if you don't want to block if the buffer is full with your operation, just implement a tryDeposit method that throws an exception if it can't store a value, or return a boolean success value.
Although in your case if you have to store the time necessary before you could push the value onto the stack, I don't see why a simple:
long start = System.nanotime();
queue.deposit();
long end = System.nanotime();
wouldn't do the trick as well.
If I understand you correctly, you are asking how to make a thread wait until it's OK to call deposit() or wait until it's OK to call fetch(). But, there's no need for that. Your deposit() method will block the calling thread until there is room in the queue, and your fetch() method will block the caller until there is something to fetch. That's what the notFull.await() and notEmpty.await() calls do.
await() unlocks the lock, sleeps until the condition is signalled by another thread, and then it re-locks the lock. The condition may or may not still be true when the caller finally gets the lock again, but that's why you have the await() calls in loops, so that the thread keeps trying until finally it has the lock and the condition is true. Then it does its work (add an item or remove an item), unlocks the lock, and returns.

Categories