Java synchronizing based on a parameter (named mutex/lock)

后端 未结 8 1781
逝去的感伤
逝去的感伤 2020-12-05 02:50

I\'m looking for a way to synchronize a method based on the parameter it receives, something like this:

public synchronized void doSomething(name){
//some co         


        
8条回答
  •  抹茶落季
    2020-12-05 03:19

    TL;DR:

    I use ConcurrentReferenceHashMap from the Spring Framework. Please check the code below.


    Although this thread is old, it is still interesting. Therefore, I would like to share my approach with Spring Framework.

    What we are trying to implement is called named mutex/lock. As suggested by Tudor's answer, the idea is to have a Map to store the lock name and the lock object. The code will look like below (I copy it from his answer):

    Map locks = new HashMap();
    locks.put("a", new Object());
    locks.put("b", new Object());
    

    However, this approach has 2 drawbacks:

    1. The OP already pointed out the first one: how to synchronize the access to the locks hash map?
    2. How to remove some locks which are not necessary anymore? Otherwise, the locks hash map will keep growing.

    The first problem can be solved by using ConcurrentHashMap. For the second problem, we have 2 options: manually check and remove locks from the map, or somehow let the garbage collector knows which locks are no longer used and the GC will remove them. I will go with the second way.

    When we use HashMap, or ConcurrentHashMap, it creates strong references. To implement the solution discussed above, weak references should be used instead (to understand what is a strong/weak reference, please refer to this article or this post).


    So, I use ConcurrentReferenceHashMap from the Spring Framework. As described in the documentation:

    A ConcurrentHashMap that uses soft or weak references for both keys and values.

    This class can be used as an alternative to Collections.synchronizedMap(new WeakHashMap>()) in order to support better performance when accessed concurrently. This implementation follows the same design constraints as ConcurrentHashMap with the exception that null values and null keys are supported.

    Here is my code. The MutexFactory manages all the locks with is the type of the key.

    @Component
    public class MutexFactory {
    
        private ConcurrentReferenceHashMap map;
    
        public MutexFactory() {
            this.map = new ConcurrentReferenceHashMap<>();
        }
    
        public Object getMutex(K key) {
            return this.map.compute(key, (k, v) -> v == null ? new Object() : v);
        }
    }
    

    Usage:

    @Autowired
    private MutexFactory mutexFactory;
    
    public void doSomething(String name){
        synchronized(mutexFactory.getMutex(name)) {
            // ...
        }
    }
    

    Unit test (this test uses the awaitility library for some methods, e.g. await(), atMost(), until()):

    public class MutexFactoryTests {
        private final int THREAD_COUNT = 16;
    
        @Test
        public void singleKeyTest() {
            MutexFactory mutexFactory = new MutexFactory<>();
            String id = UUID.randomUUID().toString();
            final int[] count = {0};
    
            IntStream.range(0, THREAD_COUNT)
                    .parallel()
                    .forEach(i -> {
                        synchronized (mutexFactory.getMutex(id)) {
                            count[0]++;
                        }
                    });
            await().atMost(5, TimeUnit.SECONDS)
                    .until(() -> count[0] == THREAD_COUNT);
            Assert.assertEquals(count[0], THREAD_COUNT);
        }
    }
    

提交回复
热议问题