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
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:
locks
hash map?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);
}
}