Search code examples
javamultithreadingreentrantlock

Check if lock is held but not lock it if it is free


I have scenario where i have multiple counters object. Each counters object can be incremented by multiple threads at the same time so there is a set of ReentrantLock for all objects and this works good - each object can be modified only by one thread at given moment.

Here's the catch: there is a process which runs every 15 minutes and collects all counters object, do some calculations and clears counters. This thread is not locking anything so there are situations as below:

  1. incrementing_thread is fetching counters object and incrementing some counters
  2. clearing_thread is fetching all counter objects, do some calculations and clears counters
  3. clearing_thread saves counters objects to cache
  4. incrementing_thread saves counters object to cache

In such situations one of the counters object is messed up because at the end clearing operation is discarded and the state of counters are same as before clearing.

What i want to:

  • All incrementing_threads are locking on specific counters object so each object can be modified only by one thread but at the same time independent object can be modified by multiple thread and this work already great.
  • When clearing_thread starts it sets some sort of flag which is read by all incrementing_threads and they have to wait until the flag is dismissed.

I have backup plans:

  • clearing_thread lock on all objects but i don't like this idea because it can take too long and if it block on one of the object it could potentially block all threads.
  • I could clear counters in for loop for each object but then while clearing one object the other objects can be modified and this is not ideal for me.

As you can see i have some options but i'm wondering if there is better way to do this.

UPDATE I was asked for the code so there it is.

Below example of one of the methods that increments counters on object.

public void sipIncomingCall(String objName) {
    try {
        lock(objName);
        Stats stat = getStatisticsForObj(objName);
        long l = stat.getSipIncomingConnections().incrementAndGet();
        stat.getSipConnectionsSum().incrementAndGet();
        LOGGER.debug("incrementing sip incoming connections to {}, objName {}", l, objName);
        putStatisticsForObj(objName, stat);
    }finally {
        unlock(objName);
    }
}

lock() and unlock() methods:

private Map<String,ReentrantLock> locks = new ConcurrentHashMap<>();
protected void lock(String key) {
    ReentrantLock lock = locks.getOrDefault(key, new ReentrantLock());
    lock.lock();
}

protected void unlock(String key){
    ReentrantLock lock = locks.get(key);
    if(lock!=null){
        lock.unlock();
    }
}

methods getStatisticsForObj() and putStatisticsForObj():

private MgcfStats getStatisticsForObj(String tgName) {
    //get object from local cache (or hazelcast)
    return Cluster.getTgStatistics(tgName);
}

private void putStatisticsForObj(String tgName,MgcfStats stats){
    //saving to local cache and hazelcast
    Cluster.putTgStatistics(tgName,stats);
}

Below is fragment from "clearing_thread" which copies all statistics objects to local map and then clearing statistics in Cluster:

    statisticsData.setObjStats(new HashMap<>(Cluster.getTgStatistics()));
    Cluster.clearTgStatistics();

Solution

  • You may use ReadWriteLock.

    • Incrementing threads acquire read lock before incrementing value.
    • Cleaning thread acquire write lock.

    You still need individual locks for each counter.