1

I have two classes each with one method.

class A {
    private void insert(String usedId){
        // ...
    }
}

class B {
    private void refresh(String userId){
        // ...
    }
}

Each method is called from a different Thread. They are called from different Threads for different userIds.

I need to lock the first method when second is called and vise versa for the same userId.

Is the best choice to hold a List of ids and set the lock?

Neuron
  • 5,141
  • 5
  • 38
  • 59
elnino
  • 235
  • 3
  • 12
  • 4
    No. Best choice to to specify your use case not specify what you want to do. This sounds a lot like an XY problem. For example - if both methods update a map the the map can be made threadsafe, etc. Take a step back - what is your use case? – Boris the Spider Apr 10 '18 at 16:37
  • 3
    There are ways of doing this, but first I'd probably question why two seemingly independent classes need to lock the same resource. – biziclop Apr 10 '18 at 16:38
  • I have API call modification of user and API call of periodic update. I need to serialize calls when are called at same time and add delay between modification and periodic update. – elnino Apr 10 '18 at 17:16

1 Answers1

1

We introduce a LockDispenser. You pass this object to all As and Bs you want to have thread safe. It will provide Lock objects with createLock(String forId) which need to be released after use by calling releaseLock(String forId).

public class LockDispenser {
    private final Map<String, Lock> dispenser = new LinkedHashMap<>();

    public Object createLock(String forId) {
        synchronized (dispenser) {
            if (!dispenser.containsKey(forId)) {
                dispenser.put(forId, new Lock());
            }
            Lock lock = dispenser.get(forId);
            lock.referenceCounter++;
            return lock;
        }
    }

    public void releaseLock(String forId) {
        synchronized (dispenser) {
            Lock lock = dispenser.get(forId);
            lock.referenceCounter--;
            if (lock.referenceCounter == 0) {
                dispenser.remove(forId);
            }
        }
    }

    public static class Lock {
        private int referenceCounter = 0;
    }
}

Now the actual thread safety comes from using the Lock in a synchronized block.

public class  A {
    private LockDispenser dispenser;

    public A(LockDispenser dispenser) {
        this.dispenser = dispenser;
    }

    private void insert(String userId) {
        synchronized (dispenser.createLock(userId)) {
            // code
        }
        dispenser.releaseLock(userId); // consider putting this in a finally block
    }
}

public class B {
    private LockDispenser dispenser;

    public B(LockDispenser dispenser) {
        this.dispenser = dispenser;
    }

    private void refresh(String userId) {
        synchronized (dispenser.createLock(userId)) {
            // code
        }
        dispenser.releaseLock(userId); // consider putting this in a finally block
    }
}

Make sure releaseLock(String forId) is called even if an Exception is thrown. You can do this by putting it into a finally block.

And create them like such:

public static void main(String... args) {
    LockDispenser fooLock = new LockDispenser();
    A fooA = new A(fooLock);
    B fooB = new B(fooLock);


    LockDispenser barLock = new LockDispenser();
    A barA = new A(barLock);
    B barB = new B(barLock);
}

fooA and fooB are thread safe with each other and so are barA and barB.

Neuron
  • 5,141
  • 5
  • 38
  • 59
  • You are going to need to use `finally` to release the locks, otherwise when errors occur then locks will be left dangling. – Boris the Spider Apr 10 '18 at 17:19
  • 1. What is `lock.referenceCounter`? I reckon you're not referring to `java.util.concurrent.locks.Lock`, which you should be as there's no particular reason to reinvent the wheel in this case 2. Why not use `ConcurrentHashMap.merge` instead of synchronizing on a non thread-safe map? – crizzis Apr 10 '18 at 18:12