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 code
}
I want the method doSomething
to be synchronized based on the name
parameter like this:
Thread 1: doSomething("a");
Thread 2: doSomething("b");
Thread 3: doSomething("c");
Thread 4: doSomething("a");
Thread 1 , Thread 2 and Thread 3 will execute the code without being synchronized , but Thread 4 will wait until Thread 1 has finished the code because it has the same "a" value.
Thanks
UPDATE
Based on Tudor explanation I think I'm facing another problem: here is a sample of the new code:
private HashMap locks=new HashMap();
public void doSomething(String name){
locks.put(name,new Object());
synchronized(locks.get(name)) {
// ...
}
locks.remove(name);
}
The reason why I don't populate the locks map is because name can have any value.
Based on the sample above , the problem can appear when adding / deleting values from the hashmap by multiple threads in the same time, since HashMap is not thread-safe.
So my question is if I make the HashMap
a ConcurrentHashMap
which is thread safe, will the synchronized block stop other threads from accessing locks.get(name) ??
I've used a cache to store lock objects. The my cache will expire objects after a period, which really only needs to be longer that the time it takes the synchronized process to run
`
`
I have a much simpler, scalable implementation akin to @timmons post taking advantage of guavas
LoadingCache
withweakValues
. You will want to read the help files on "equality" to understand the suggestion I have made.Define the following weakValued cache.
Now! As a result of the JVM, we do not have to worry that the cache is growing too large, it only holds the cached strings as long as necessary and the garbage manager/guava does the heavy lifting.
I've created a tokenProvider based on the IdMutexProvider of McDowell. The manager uses a
WeakHashMap
which takes care of cleaning up unused locks.You could find my implementation here.
The answer of Tudor is fine, but it's static and not scalable. My solution is dynamic and scalable, but it goes with increased complexity in the implementation. The outside world can use this class just like using a
Lock
, as this class implements the interface. You get an instance of a parameterized lock by the factory methodgetCanonicalParameterLock
.Of course you'd need a canonical key for a given parameter, otherwise threads would not be synchronized as they would be using a different Lock. Canonicalization is the equivalent of the internalization of Strings in Tudor's solution. Where
String.intern()
is itself thread-safe, my 'canonical pool' is not, so I need extra synchronization on the WeakHashMap.This solution works for any type of Object. However, make sure to implement
equals
andhashCode
correctly in custom classes, because if not, threading issues will arise as multiple threads could be using different Lock objects to synchronize on!The choice for a WeakHashMap is explained by the ease of memory management it brings. How else could one know that no thread is using a particular Lock anymore? And if this could be known, how could you safely delete the entry out of the Map? You would need to synchronize upon deletion, because you have a race condition between an arriving thread wanting to use the Lock, and the action of deleting the Lock from the Map. All these things are just solved by using weak references, so the VM does the work for you, and this simplifies the implementation a lot. If you inspected the API of WeakReference, you would find that relying on weak references is thread-safe.
Now inspect this test program (you need to run it from inside the ParameterLock class, due to private visibility of some fields):
Chances are very high that you would see that both threads are using the same lock object, and so they are synchronized. Example output:
However, with some chance it might be that the 2 threads do not overlap in execution, and therefore it is not required that they use the same lock. You could easily enforce this behavior in debugging mode by setting breakpoints at the right locations, forcing the first or second thread to stop wherever necessary. You will also notice that after the Garbage Collection on the main thread, the WeakHashMap will be cleared, which is of course correct, as the main thread waited for both worker threads to finish their job by calling
Thread.join()
before calling the garbage collector. This indeed means that no strong reference to the (Parameter)Lock can exist anymore inside a worker thread, so the reference can be cleared from the weak hashmap. If another thread now wants to synchronize on the same parameter, a new Lock will be created in the synchronized part ingetCanonicalParameterLock
.Now repeat the test with any pair that has the same canonical representation (= they are equal, so
a.equals(b)
), and see that it still works:etc.
Basically, this class offers you the following functionality:
equals
andhashCode
is implemented properly)This Lock implementation has been tested by modifying an ArrayList concurrently with 10 threads iterating 1000 times, doing this: adding 2 items, then deleting the last found list entry by iterating the full list. A lock is requested per iteration, so in total 10*1000 locks will be requested. No ConcurrentModificationException was thrown, and after all worker threads have finished the total amount of items was 10*1000. On every single modification, a lock was requested by calling
ParameterLock.getCanonicalParameterLock(new String("a"))
, so a new parameter object is used to test the correctness of the canonicalization.Please note that you shouldn't be using String literals and primitive types for parameters. As String literals are automatically interned, they always have a strong reference, and so if the first thread arrives with a String literal for its parameter then the lock pool will never be freed from the entry, which is a memory leak. The same story goes for autoboxing primitives: e.g. Integer has a caching mechanism that will reuse existing Integer objects during the process of autoboxing, also causing a strong reference to exist. Addressing this, however, this is a different story.
Use a map to associate strings with lock objects:
then:
Check out this framework. Seems you're looking for something like this.
https://code.google.com/p/jkeylockmanager/