Race condition in Resource ID based locking manager - java

I'm trying to write a locking manager that will be called from multiple threads. This manager handles locking based on various resource IDs. These can vary very much, so keeping a lock in memory for each one would probably cause large memory usage. This is why after a lock is no longer used (the number of threads using it reaches 0), it is removed from memory.
It can exclusively lock threads based on the requested resource ID (if two threads lock the same ID, one will wait for the other to unlock it), or completely exclude all other threads using a ReentrantReadWriteLock.
I am experiencing a race condition where a lock is removed from memory when unlocked by the last thread that holds it, but other threads still try to unlock it? This results in a NPE which I cannot explain.
I have tried using AtomicInteger instead of the current volatile variable, thinking it may have something to do with that but it had similar results.
Here is the problematic class:
/**
* This class provides locks for reading and writing, and bulk operations lock on the entire class.
*
* If a bulk operation is not in progress, class based locking is transparent.
* #author KiralyCraft
*
*/
public class ReadWriteHighLevelLocking
{
private class Semaphore
{
private ReentrantLock lock;
private volatile int acquiredLocks;
public Semaphore()
{
this.acquiredLocks = 0;
this.lock = new ReentrantLock();
}
public synchronized int incrementAndGet()
{
return ++acquiredLocks;
}
public synchronized int decrementAndGet()
{
return --acquiredLocks;
}
}
private ReentrantReadWriteLock classBasedLock;
private volatile HashMap<String, Semaphore> stateChangeLocks;
public ReadWriteHighLevelLocking()
{
this.stateChangeLocks = new HashMap<String,Semaphore>();
this.classBasedLock = new ReentrantReadWriteLock();
}
/**
* Acquires a lock for the specified resource ID.
*
* May block if another thread is currently holding a bulk lock.
* #param resourceID
*/
public void acquireLock(String resourceID)
{
classBasedLock.readLock().lock(); //Using it reversed. There can be any number of operations (using the read lock), but only one bulk operation (sacrifice)
Semaphore stateChangeLock;
synchronized(stateChangeLocks)
{
if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
{
stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
}
}
stateChangeLock.lock.lock();
stateChangeLock.incrementAndGet();
}
public void releaseLock(String resourceID)
{
Semaphore stateChangeLock;
synchronized(stateChangeLocks)
{
stateChangeLock = stateChangeLocks.get(resourceID);
if (stateChangeLock.decrementAndGet() == 0) //<----------------- HERE IS THE NPE
{
stateChangeLocks.remove(resourceID);
}
}
stateChangeLock.lock.unlock();
classBasedLock.readLock().unlock();
}
/**
* When a bulk lock is acquired, all other operations are delayed until this one is released.
*/
public void acquireBulkLock()
{
classBasedLock.writeLock().lock(); //Using it reversed. There can be any number of writers (using the read lock), but only one reader (sacrifice)
}
public void releaseBulkLock()
{
classBasedLock.writeLock().unlock();
}
}
Sample caller class:
public abstract class AbstractDatabaseLockingController
{
...
private ReadWriteHighLevelLocking highLevelLock;
public AbstractDatabaseLockingController(DatabaseInterface db)
{
this.db = db;
this.highLevelLock = new ReadWriteHighLevelLocking();
}
...
public <T extends DatabaseIdentifiable> boolean executeSynchronizedUpdate(T theEntity,AbstractSynchronousOperation<T> aso)
{
boolean toReturn;
String lockID = theEntity.getId()+theEntity.getClass().getSimpleName();
highLevelLock.acquireLock(lockID);
toReturn = aso.execute(theEntity,db);
highLevelLock.releaseLock(lockID);
return toReturn;
}
...
public <T extends DatabaseIdentifiable> List<T> executeSynchronizedGetAllWhereFetch(Class<T> objectType, DatabaseQuerySupplier<T> dqs)
{
List<T> toReturn;
highLevelLock.acquireBulkLock();
toReturn = db.getAllWhere(objectType, dqs);
highLevelLock.releaseBulkLock();
return toReturn;
}
}
NOTE: All places where such a locking manager is used follow the acquire/release pattern from the sample class. It's basically the only place where it is used. Other threads may call the above methods indirectly through the sample class's children

I seem to have fixed the issue by updating the following code:
synchronized(stateChangeLocks)
{
if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
{
stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
}
}
stateChangeLock.lock.lock();
stateChangeLock.incrementAndGet();
to
synchronized(stateChangeLocks)
{
if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
{
stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
}
stateChangeLock.incrementAndGet();
}
stateChangeLock.lock.lock();

Related

In Java how to synchronize cache read and write operations

I am working on implementing a simple cache using ArrayList in my application.
I would like to synchronize cache update operations, while updating the cache I should not allow to perform read operations. So once cache update is completed, then only cache should allow to read.
ContextManager.java
public class ContextManager{
private List<String> trashCanIds;
public List<String> getIds() {
return ids;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
}
ConfigManager.java
public class ConfigManager{
ContextManager ctxManager = new ContextManager();
public synchronized List<String> loadIds() throws Exception {
Utils utils = new Utils();
List<String> listIds = null;
String[] ids = utils.fetchIds();
if(Objects.nonNull(ids) && ids.length > 0) {
listIds = new ArrayList<>(Arrays.asList(ids[0].split(",")));
}
ctxManager.setIds(idsList);
return idsList;
}
}
DeleteManager.java
public class DeleteManager {
ConfigManager configManager = new ConfigManager();
configManager.loadIds();
}
TestManager.java
public class TestManager {
ContextManager contextManager = new ContextManager();
contextManager.getIds();
}
In this code I have synchronized the loadIds() method.
Need help, how to prevent reading getIds() while loadIds() in progress.
You could achieve this by using the ReadWriteLock interface implemented with a ReentrantReadWriteLock instance. This class can represent your case of read and write by acquiring the corresponding lock when performing the getIds and loadIds operations. In fact,
A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing. The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html
Basically, your loadIds should acquire the write-lock before proceeding with its operations. If it succeeds, it immediately acquires the lock and carries on with its computation; otherwise it blocks the corresponding thread until the lock is obtained or an InterruptedException is thrown.
On the other hand, the getIds method should acquire the read-lock instead. Where the current thread immediately obtains the lock if this is available; otherwise it blocks the corresponding thread until the lock is obtained or an InterruptedException is thrown.
ContextManager.java
public class ContextManager{
private List<String> trashCanIds;
private ReadWriteLock lock;
private Lock readLock;
private Lock writeLock;
public ContextManager(){
lock = new ReentrantReadWriteLock(true);
readLock = lock.readLock();
writeLock = lock.writeLock();
}
public List<String> getIds() {
readLock.lock();
try {
List<String> tempTrashCanIds = new ArrayList(trashCanIds);
} finally {
readLock.unlock();
}
return tempTrashCanIds;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
public void readLock(){
this.readLock.lock();
}
public void readUnlock(){
this.readLock.unlock();
}
public void writeLock(){
this.writeLock.lock();
}
public void writeUnlock(){
this.writeLock.unlock();
}
}
ConfigManager.java
public class ConfigManager{
ContextManager ctxManager = new ContextManager();
public List<String> loadIds() throws Exception {
Utils utils = new Utils();
List<String> listIds = null;
String[] ids = utils.fetchIds();
if(Objects.nonNull(ids) && ids.length > 0) {
listIds = new ArrayList<>(Arrays.asList(ids[0].split(",")));
}
ctxManager.writeLock();
try {
ctxManager.setIds(idsList);
} finally {
ctxManager.writeUnlock();
}
return idsList;
}
}

Volatile or Synchronized in java

We have a class CalcStrategySet, which is accessed in a concurrent environment by multiple threads. I was wondering if you guys can advise which implementation is better from the 2 listed below. In the first one I have declared the variables as volatile and made the flip method, which is atomic operation as Synchronized. Other implementation I have not declared the variables as volatile but made all other methods as synchronized.
Do these methods serve the purpose and not result in race condition and if yes then which one is better?
WITH VOLATILE & SYNCHRONIZED ATOMIC METHOD
public class CalcStrategySet {
private volatile CalcStrategy current;
private volatile CalcStrategy backup;
private volatile boolean isBackup;
public CalcStrategySet(CalcStrategy current, CalcStrategy backup) {
this.current = current;
this.backup = backup;
}
public void isStandard() {
return !isBackup;
}
public void merge(CalcStrategy other) {
current.merge(other);
}
public synchronized void flip() {
if(!isBackup) {
current = backup;
backup = null;
isBackup = true;
} else {
throw new IllegalStateException("Already in backup mode");
}
}
}
FULLY SYNCHRONIZED
public class CalcStrategySet {
private CalcStrategy current;
private CalcStrategy backup;
private boolean isBackup;
public CalcStrategySet(CalcStrategy current, CalcStrategy backup) {
this.current = current;
this.backup = backup;
}
public synchronized void isStandard() {
return !isBackup;
}
public synchronized void merge(CalcStrategy other) {
current.merge(other);
}
public synchronized void flip() {
if(!isBackup) {
current = backup;
backup = null;
isBackup = true;
} else {
throw new IllegalStateException("Already in backup mode");
}
}
}
Do these methods serve the purpose and not result in race condition and if yes then which one is better?
It's hard to determine this without knowing fully how the class is used as well as what the CalcStrategy.merge() method does. If the merge() is reentrant already then the first mechanism seems adequate. There are certainly race conditions that may mean that the current gets updated right before the flip(...) method gets to the switch.
The worry is that the flip(...) method might move to the backup but have not switched isBackup to be true. For example, one thread could be in the middle of flip(...), the current has been set to the backup, but another thread might still see isStandard() returning true because isBackup has not been set yet. This is exacerbated with the multiple volatile fields.
A way to improve this would be to use an AtomicReference for the current. That would reduce that race condition and means that flip() doesn't need to be synchronized. In the below code, standard and backup are final and not volatile. Not sure if that breaks something. There still is the chance that isStandard() gets called right before flip() makes the change but it is an improvement.
Something like:
private final AtomicReference<CalcStrategy> current = new AtomicReference<>();
private final CalcStrategy standard;
private final CalcStrategy backup;
// no boolean
public CalcStrategySet(CalcStrategy standard, CalcStrategy backup) {
this.current.set(standard);
this.standard = standard;
this.backup = backup;
}
public void isStandard() {
// no race with isBackup set in flip()
return (current.get() == standard);
}
// no need for synchronized because one single atomic operation
public void flip() {
// update to the backup atomically
if(!current.compareAndSet(standard, backup)) {
throw new IllegalStateException("Already in backup mode");
}
}

Does synchronisation with ConcurrentHashMap's .compute() guarantee visibility?

Within ConcurrentHashMap.compute() I increment and decrement some long value located in shared memory. Read, increment/decrement only gets performed within compute method on the same key.
So the access to long value is synchronised by locking on ConcurrentHashMap segment, thus increment/decrement is atomic. My question is: Does this synchronisation on a map guarantee visibility for long value? Can I rely on Map's internal synchronisation or should I make my long value volatile?
I know that when you explicitly synchronise on a lock, visibility is guaranteed. But I do not have perfect understanding of ConcurrentHashMap internals. Or maybe I can trust it today but tomorrow ConcurrentHashMap's internals may somehow change: exclusive access will be preserved, but visibility will disappear... and it is an argument to make my long value volatile.
Below I will post a simplified example. According to the test there is no race condition today. But can I trust this code long-term without volatile for long value?
class LongHolder {
private final ConcurrentMap<Object, Object> syncMap = new ConcurrentHashMap<>();
private long value = 0;
public void increment() {
syncMap.compute("1", (k, v) -> {
if (++value == 2000000) {
System.out.println("Expected final state. If this gets printed, this simple test did not detect visibility problem");
}
return null;
});
}
}
class IncrementRunnable implements Runnable {
private final LongHolder longHolder;
IncrementRunnable(LongHolder longHolder) {
this.longHolder = longHolder;
}
#Override
public void run() {
for (int i = 0; i < 1000000; i++) {
longHolder.increment();
}
}
}
public class ConcurrentMapExample {
public static void main(String[] args) throws InterruptedException {
LongHolder longholder = new LongHolder();
Thread t1 = new Thread(new IncrementRunnable(longholder));
Thread t2 = new Thread(new IncrementRunnable(longholder));
t1.start();
t2.start();
}
}
UPD: adding another example which is closer to the code I am working on. I would like to remove map entries when no one else is using the object. Please note that reading and writing of the long value happens only inside of remapping function of ConcurrentHashMap.compute:
public class ObjectProvider {
private final ConcurrentMap<Long, CountingObject> map = new ConcurrentHashMap<>();
public CountingObject takeObjectForId(Long id) {
return map.compute(id, (k, v) -> {
CountingObject returnLock;
returnLock = v == null ? new CountingObject() : v;
returnLock.incrementUsages();
return returnLock;
});
}
public void releaseObjectForId(Long id, CountingObject o) {
map.compute(id, (k, v) -> o.decrementUsages() == 0 ? null : o);
}
}
class CountingObject {
private int usages;
public void incrementUsages() {
--usages;
}
public int decrementUsages() {
return --usages;
}
}
UPD2: I admit that I failed to provide the simplest code examples previously, posting a real code now:
public class LockerUtility<T> {
private final ConcurrentMap<T, CountingLock> locks = new ConcurrentHashMap<>();
public void executeLocked(T entityId, Runnable synchronizedCode) {
CountingLock lock = synchronizedTakeEntityLock(entityId);
try {
lock.lock();
try {
synchronizedCode.run();
} finally {
lock.unlock();
}
} finally {
synchronizedReturnEntityLock(entityId, lock);
}
}
private CountingLock synchronizedTakeEntityLock(T id) {
return locks.compute(id, (k, l) -> {
CountingLock returnLock;
returnLock = l == null ? new CountingLock() : l;
returnLock.takeForUsage();
return returnLock;
});
}
private void synchronizedReturnEntityLock(T lockId, CountingLock lock) {
locks.compute(lockId, (i, v) -> lock.returnBack() == 0 ? null : lock);
}
private static class CountingLock extends ReentrantLock {
private volatile long usages = 0;
public void takeForUsage() {
usages++;
}
public long returnBack() {
return --usages;
}
}
}
No, this approach will not work, not even with volatile. You would have to use AtomicLong, LongAdder, or the like, to make this properly thread-safe. ConcurrentHashMap doesn't even work with segmented locks these days.
Also, your test does not prove anything. Concurrency issues by definition don't happen every time. Not even every millionth time.
You must use a proper concurrent Long accumulator like AtomicLong or LongAdder.
Do not get fooled by the line in the documentation of compute:
The entire method invocation is performed atomically
This does work for side-effects, like you have in that value++; it only works for the internal data of ConcurrentHashMap.
The first thing that you miss is that locking in CHM, the implementation has changed a lot (as the other answer has noted). But even if it did not, your understanding of the:
I know that when you explicitly synchronize on a lock, visibility is guaranteed
is flawed. JLS says that this is guaranteed when both the reader and the writer use the same lock; which in your case obviously does not happen; as such no guarantees are in place. In general happens-before guarantees (that you would require here) only work for pairs, for both reader and writer.

Adding to a message queue if more important messages come in

We need to send messages with highest priority first so we use a PriorityQueue for our purpose.
PriorityQueue<MessageData> queue = new PriorityQueue<MessageData>();
However, we also want our queue to behave like a sorted set as well. Therefore, we adapt the PriorityQueue to ignore insertions which repeat existing members.
import java.util.Comparator;
import java.util.PriorityQueue;
public class PrioritySet<E> extends PriorityQueue<E> {
private static final long serialVersionUID = 34658778L;
public PrioritySet() {
super();
}
public PrioritySet(int initialCapacity, Comparator<? super E> comparator) {
super(initialCapacity, comparator);
}
#Override
public boolean offer(E e) {
boolean isAdded = false;
if(!super.contains(e)) {
isAdded = super.offer(e);
}
return isAdded;
}
}
Now our app specific implementation of the data structure.
import java.util.Comparator;
public class MessagePrioritySet extends PrioritySet<MessageData> {
private static final long serialVersionUID = 34658779L;
private int minPriorityNumber;
public MessagePrioritySet() {
super();
}
public MessagePrioritySet(int initialCapacity, Comparator<MessageData> comparator) {
super(initialCapacity, comparator);
}
public synchronized int getMinPriorityNumber() {
return minPriorityNumber;
}
public synchronized void setMinPriorityNumber(int minPriorityNumber) {
this.minPriorityNumber = minPriorityNumber;
}
#Override
public synchronized boolean offer(MessageData notification) {
boolean isAdded = super.offer(notification);
if (notification.getPriority() < minPriorityNumber)
minPriorityNumber = notification.getPriority();
return isAdded;
}
public synchronized void reportSent(MessageData notification) {
MessageData nextMessageData = peek();
if (nextMessageData == null)
minPriorityNumber = 0;
else if (nextMessageData.getPriority() > notification.getPriority())
minPriorityNumber = nextMessageData.getPriority();
}
}
Here, we want the data structure to be aware of the minimum priority value of the messages so we declare an instance variable for that. The priority of the incoming message is checked and if this priority is lower than the stored value, the value stored is updated. The use of the class is required to report any sent messages. If no other member of the data structure has a priority as low as the one being removed, then the next element's priority becomes the stored priority.
Two threads share the implemented queue. One thread fetches data from the database and inserts them into the queue. The other reads the queue and sends the highest priority message with the lowest priority number. Because the queue sets the minimum priority value to 0 and the thread which fetches data from the database reads rows with priority value lower than or equal to the minimum value stored in the queue if the stored minimum value is not zero, we can be pretty sure that while the current messages in the queue are being sent, only the new messages which are more important than those already in the queue will be added to the queue.
We think that the operations in the while loops in the threads should be atomic and would thank anyone who could tell us how to make them atomic.
private void startMptSender() {
sleepInterval = 1000;
final MessagePrioritySet messagePrioritySet = new MessagePrioritySet();
Runnable mptReader = new Runnable() {
#Override
public void run() {
while (true) {
List<MessageData> messageDataList;
if (messagePrioritySet.getMinPriorityNumber() == 0)
messageDataList = messageDao.readSMSMpt();
else
messageDataList = messageDao.readSMSMpt(messagePrioritySet.getMinPriorityNumber());
for (MessageData messageData : messageDataList) {
messagePrioritySet.offer(messageData);
}
try {
Thread.sleep(sleepInterval);
} catch (InterruptedException ie) {
}
}
}
};
executor.execute(mptReader);
Runnable mptPusher = new Runnable() {
#Override
public void run() {
while (status) {
if (messagePrioritySet.size() > 0) {
while (messagePrioritySet.size() != 0) {
MessageData noti = messagePrioritySet.remove();
mptSender.sendSms(noti);
messageDao.markNotificationAsRead(noti.getSyskey());
messagePrioritySet.reportSent(noti);
try {
Thread.sleep(sleepInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} else {
try {
Thread.sleep(sleepInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
executor.execute(mptPusher);
}
}
I assume what you mean with atomic translates to: you want that each thread is doing all of its work for one iteration without being interrupted by the other thread.
In other words: you have (probably multiple) operations; and while thread A is doing his operations, thread B shouldn't be doing anything - because you want to make sure that B only sees the "complete set" of updates made by A.
Sure, when that operation would be just about writing to one int for example, you could be using AtomicInteger for example. But when you are talking about several operations ... you need something else.
A "brute force" solution would be to add some sort of locking. Meaning: your threads share some LOCK object; and whenever one thread enters a "critical section" ... it needs to acquire that LOCK first (and of course release directly afterwards). But this will need very careful designing; as want to make sure that thread A isn't "starving" B by holding that lock for too long.
Looking at your code again, more closely ... maybe you could try to make your minPriority to be an AtomicInteger; the question is how that would relate to the other thread that is working the "size" of your queue.

Thread synchronization based upon an id

I need a way to allow only one thread to modify data related to a service ticket. More than one thread may be attempting to modify the ticket data at the same time.
Below is a simplified version of my approach. Is there a better way to do this? Maybe with java.util.concurrent packages?
public class SomeClass1
{
static final HashMap<Integer, Object> ticketLockMap = new HashMap<Integer, Object>();
public void process(int ticketNumber)
{
synchronized (getTicketLock(ticketNumber))
{
// only one thread may modify ticket data here
// ... ticket modifications here...
}
}
protected static Object getTicketLock(int ticketNumber)
{
Object ticketLock;
// allow only one thread to use map
synchronized (ticketLockMap)
{
ticketLock = ticketLockMap.get(ticketNumber);
if (ticketLock == null)
{
// first time ticket is locked
ticketLock = new Object();
ticketLockMap.put(ticketNumber, ticketLock);
}
}
return ticketLock;
}
}
Additionally, if I don't want the HashMap filling up with unused locks, I would need a more complex approach like the following:
public class SomeClass2
{
static final HashMap<Integer, Lock> ticketLockMap = new HashMap<Integer, Lock>();
public void process(int ticketNumber)
{
synchronized (getTicketLock(ticketNumber))
{
// only one thread may modify ticket data here
// ... ticket modifications here...
// after all modifications, release lock
releaseTicketLock(ticketNumber);
}
}
protected static Lock getTicketLock(int ticketNumber)
{
Lock ticketLock;
// allow only one thread to use map
synchronized (ticketLockMap)
{
ticketLock = ticketLockMap.get(ticketNumber);
if (ticketLock == null)
{
// first time ticket is locked
ticketLock = new Lock();
ticketLockMap.put(ticketNumber, ticketLock);
}
}
return ticketLock;
}
protected static void releaseTicketLock(int ticketNumber)
{
// allow only one thread to use map
synchronized (ticketLockMap)
{
Lock ticketLock = ticketLockMap.get(ticketNumber);
if (ticketLock != null && --ticketLock.inUseCount == 0)
{
// lock no longer in use
ticketLockMap.remove(ticketLock);
}
}
}
}
class Lock
{
// constructor/getters/setters omitted for brevity
int inUseCount = 1;
}
You might be looking for the Lock interface. The second case could be solved by a ReentrantLock, which counts the number of times it has been locked.
Locks have a .lock() method which waits for the lock to acquire and an .unlock method which should be called like
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
This could then be combined with a HashMap<Integer, Lock>. You could omit the synchronized calls and cut down on lines of code.

Categories

Resources