Are these methods getNewId() & fetchIdsInReserve() thread safe ?
public final class IdManager {
private static final int NO_OF_USERIDS_TO_KEEP_IN_RESERVE = 200;
private static final AtomicInteger regstrdUserIdsCount_Cached = new AtomicInteger(100);
private static int noOfUserIdsInReserveCurrently = 0;
public static int getNewId(){
synchronized(IdManager.class){
if (noOfUserIdsInReserveCurrently <= 20)
fetchIdsInReserve();
noOfUserIdsInReserveCurrently--;
}
return regstrdUserIdsCount_Cached.incrementAndGet();
}
private static synchronized void fetchIdsInReserve(){
int reservedInDBTill = DBCountersReader.readCounterFromDB(....); // read column from DB
if (noOfUserIdsInReserveCurrently + regstrdUserIdsCount_Cached.get() != reservedInDBTill) throw new Exception("Unreserved ids alloted by app before reserving from DB");
if (DBUpdater.incrementCounter(....)) //if write back to DB is successful
noOfUserIdsInReserveCurrently += NO_OF_USERIDS_TO_KEEP_IN_RESERVE;
}
}
No.
If 21 threads comes in here
and wait while another 180 threads proceed through the top and through the line below, then by the time the 21st thread reaches the line below, there will be no user ids in reserve when the 21st thread from the first group calls
EDIT:
Here’s the initial state on class load:
Let’s assume that the write back to the DB is always successful. If it isn’t, this code is clearly broken, because it still allocates an ID in that case.
The first thread comes through, and calls fetch because there are no ids in reserve.
assuming the DB returns 100 as the initial ID, after the method completes without contention
Now, let’s assume 178 more threads go through without contention
if that thread is preempted by another that comes through after it exits the
synchronizedblock but before it decrements the atomicint, the preempting thread will trigger a fetch.Since
noOfUserIdsInReserveCurrentlyhas not been decremented by the thread that was pre-empted,will be false.
Assuming that exception indicates a failure mode, we have a failure during one interleaving that is not thrown during other-interleavings. Therefore, the code is not thread-safe.
The solution is to consistently access
regstrdUserIdsCount_Cachedinside the critical section. In that case, it need not be an atomic int, but can simply be a non-finalint.