How to take Re-entrant lock on String value?

1.3k Views Asked by At

I have a multi-threaded application and I want to use re-entrant lock on a String. For example if I use normal synchronization my code will look like.

I don't want two threads of same IPaddress to enter in my loadBalance() so I take lock on IP address which is working fine.

class A {
    String ipAddress;
    ...
    void loadBalance() {
        synchronized (ipAddress) {
            // Business logic goes here
        }
    }
}

Now if I use Re-entrant API here then the code will look like below.Now two threads of same IP address is entering in my code which is not required. So I need to know how can I prevent this using Re-entrant API.

class A {
    String ipAddress;
    ReentrantLock lock = new ReentrantLock();
    ...
    void loadBalance() {
        lock.lock();
            // Business logic goes here
        lock.unlock();
    }
}

My query is how could I take lock on IP address using Re-entrant lock as I am doing in synchronized block.

2

There are 2 best solutions below

0
On

Rather than have a lock per String, which may lead to an ever growing amount of locks, and ultimately maybe an OutOfMemoryError, it's better to use a striped locking strategy in this instance.

Make a finite amount of locks (let's say n), store them in an array. If you need a lock for a given String use its hashCode() and a modulo n to determine which array element contains the lock to use. Equal Strings will use the same lock.

private static final int N = 10;
private ReentrantLock[] locks = new ReentrantLocks[N];

{
    for (int i = 0; i < N; i++) {
        locks[i] = new ReentrantLock();
    }
}

...

// where you need a lock :
String ipAddress = ...

ReentrantLock lock = locks[ipAddress.hashCode() % N];
lock.lock();

The trade off is that some non equal Strings will use the same lock as well. You should test with varying amounts of n to strike a balance between unneeded lock contention and memory.

Alternatively you could use Guava's Striped class

0
On

I found a way but not sure if this one is right approach. I have found a class that will provide a lock against a String name provided

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

public class LockByName<L> {

    ConcurrentHashMap<String, L> mapStringLock;

    public LockByName() {
        mapStringLock = new ConcurrentHashMap<String, L>();
    }

    public LockByName(ConcurrentHashMap<String, L> mapStringLock) {
        this.mapStringLock = mapStringLock;
    }

    @SuppressWarnings("unchecked")
    public L getLock(String key) {
        L initValue = (L) createIntanceLock();
        L lock = mapStringLock.putIfAbsent(key, initValue);
        if (lock == null) {
            lock = initValue;
        }
        return lock;
    }

    protected Object createIntanceLock() {
        return new ReentrantLock();
    }

}

class A {
String ipAddressl
    LockByName<ReentrantLock> reentrantLocker = new LockByName<ReentrantLock>();
    ...
    ReentrantLock reentrantLock1 = reentrantLocker.getLock(ipAddress);

    try {
        reentrantLock1.lock();
        // DO WORK

    } finally {
        reentrantLock1.unlock();

    }

}