Search code examples
javamultithreadingjava-threads

Synchronization of methods of differents classes


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?


Solution

  • 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.