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

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<String, Object> locks = new HashMap<String, Object>();
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<K, Reference<V>>()) 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 <K> is the type of the key.

@Component
public class MutexFactory<K> {

    private ConcurrentReferenceHashMap<K, Object> 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<String> 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<String> 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);
    }
}

Leave a Comment