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

后端 未结 8 1728
逝去的感伤
逝去的感伤 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:00

    Check out this framework. Seems you're looking for something like this.

    public class WeatherServiceProxy {
    ...
    private final KeyLockManager lockManager = KeyLockManagers.newManager();
    
    public void updateWeatherData(String cityName, Date samplingTime, float temperature) {
            lockManager.executeLocked(cityName, new LockCallback() {
                    public void doInLock() {
                            delegate.updateWeatherData(cityName, samplingTime, temperature);
                    }
            });
    }
    

    https://code.google.com/p/jkeylockmanager/

    0 讨论(0)
  • 2020-12-05 03:05

    I have a much simpler, scalable implementation akin to @timmons post taking advantage of guavas LoadingCache with weakValues. You will want to read the help files on "equality" to understand the suggestion I have made.

    Define the following weakValued cache.

    private final LoadingCache<String,String> syncStrings = CacheBuilder.newBuilder().weakValues().build(new CacheLoader<String, String>() {
        public String load(String x) throws ExecutionException {
            return new String(x);
        }
    });
    
    public void doSomething(String x) {
          x = syncStrings.get(x);
          synchronized(x) {
              ..... // whatever it is you want to do
          }
    }
    

    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.

    0 讨论(0)
  • 2020-12-05 03:07

    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.

    0 讨论(0)
  • 2020-12-05 03:11

    Use a map to associate strings with lock objects:

    Map<String, Object> locks = new HashMap<String, Object>();
    locks.put("a", new Object());
    locks.put("b", new Object());
    // etc.
    

    then:

    public void doSomething(String name){
        synchronized(locks.get(name)) {
            // ...
        }
    }
    
    0 讨论(0)
  • 2020-12-05 03:15

    I've found a proper answer through another stackoverflow question: How to acquire a lock by a key

    I copied the answer here:

    Guava has something like this being released in 13.0; you can get it out of HEAD if you like.

    Striped more or less allocates a specific number of locks, and then assigns strings to locks based on their hash code. The API looks more or less like

    Striped<Lock> locks = Striped.lock(stripes);
    Lock l = locks.get(string);
    l.lock();
    try {
      // do stuff 
    } finally {
      l.unlock();
    }
    

    More or less, the controllable number of stripes lets you trade concurrency against memory usage, because allocating a full lock for each string key can get expensive; essentially, you only get lock contention when you get hash collisions, which are (predictably) rare.

    0 讨论(0)
  • 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<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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题