0

There are many threads which work on elements on the same map, each with a code, for example:

THREAD_1 works on an element with code "1234"
THREAD_2 works on an element with code "1234"
THREAD_3 works on an element with code "9876"
etc...

Elements are not permanent, i.e. THREAD_1 might remove the element "1234" and then insert it again. What I want is that, while THREAD_1 is working on the element "1234" (also removing it) THREAD_2 must wait.

Is there a way for doing it?

A possible solution might be to insert a fake element in a HashMap and then enforce the synchronization with a "synchronized" clause on that element. What do you think about? (Obviously that fake element would remain in the map also if a thread removed the element with the associated code)...

Massimo
  • 3,436
  • 4
  • 40
  • 68

3 Answers3

2

Given your particular problem, none of the java standard object can solve all your problems. Here's a solution that I believe is correct and does not keep any unnecessary keys or values in your lock map:

// we don't use a ConcurrentHashMap, because we have some other operations 
// that need to be performed in atomically with map.put and map.remove.
// ConcurrentHashMap would of course also work, but it doesn't remove the 
// need for external synchronization in in our case.
Map<String, CountingLock> locksMap = new HashMap<String, CountingLock>();
...

HttpResponse myFunction(String key) {

    CountingLock lock;
    synchronized(locksMap){
        lock = locksMap.get(key);
        if(lock == null){
            lock = new CountingLock();
            locksMap.put(key, lock);
        }
        lock.prepare(); // has to be done while holding the lock of locksMap.
                        // basically tells other threads that the current 
                        // thread intends to acquire the lock soon. This way,
                        // the other threads know not to remove this lock 
                        // from locksMap as long as another one has indicated
                        // that he is going to need it soon.
    }

    lock.lock(); // has to be done while NOT holding the lock of locksMap,
                 // or we risk deadlock situations.

    try {
        // ...
        // work
        // ...
    } finally {
        synchronized(locksMap) {
            if(lock.unlock() == 0){
                // no other thread is intending to use this lock any more. 
                // It is safe to remove it from the map. The next thread 
                // will just have to recreate a new lock for the same key.
                locksMap.remove(key);
            }
        }
    }

    return SOMETHING;    
}

private static class CountingLock {
    // The number of threads that are trying to access the protected Key
    private AtomicInteger interestedThreads = new AtomicInteger(0);

    private Lock lock = new ReentrantLock();

    public void prepare(){
        interestedThreads.incrementAndGet();
    }

    public void lock(){
        lock.lock();
    }

    public int unlock(){
        lock.unlock();
        return interestedThreads.decrementAndGet();              
    }
}

This code should work as expected in all cases. That was a fun problem to solve :-)

LordOfThePigs
  • 11,050
  • 7
  • 45
  • 69
  • 1
    This solution should work. Just is not so good the synchronized clause on the whole map, which could be a bottleneck. However thanks, I think so far this is the best solution... It is strange the inexistence of a named lock... – Massimo Nov 02 '12 at 23:56
  • It shouldn't be too big of a bottleneck because all of the operations inside the locks are really quick. Since you are apparently working with some HTTP related stuff, I suspect that whatever you are doing in your "Work" block is many, many times as long to execute as the contents of the synchronized blocks. Therefore, those synchronized blocks shouldn't have any measurable impact on performance. – LordOfThePigs Nov 03 '12 at 11:03
  • I agree with you. It works (obviously) and it does not appear to be a big bottleneck ;) thank you! – Massimo Nov 03 '12 at 14:24
1

We use a thing we call a LockMap.

A LockMap is essentially:

Map<Object, ReadWriteLock>

We have a synchronized method to fetch the lock for a specific Object.

Since Maps rely on equivalence, rather than identity, two objects that are equal() give you the same lock.

So:

lock1 = map.get(new Thing(1));
lock2 = map.get(new Thing(1));

lock1 == lock2 = true

Which is handy.

Once you get the lock, you can lock it as you wish to control access to the Object.

The other thing we do is use a LRU map (using a LinkedHashMap -- see this), so that old object keys can fall off the end when they're unused.

Will Hartung
  • 115,893
  • 19
  • 128
  • 203
0

You should use ConcurrentHashMap

A hash table supporting full concurrency of retrievals and adjustable expected concurrency for updates.

Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove).

The allowed concurrency among update operations is guided by the optional concurrencyLevel constructor argument (default 16), which is used as a hint for internal sizing. The table is internally partitioned to try to permit the indicated number of concurrent updates without contention. Because placement in hash tables is essentially random, the actual concurrency will vary

Amit Deshpande
  • 19,001
  • 4
  • 46
  • 72
  • 1
    Yes, I used it. But the problem is this: each element has an associated counter and it is possible to store these elements on the permanent memory (e.g. files). I need to be sure that, if someone is working on that element (which can be in memory or in a file) nobody can access it. Then, I need a generic approach... – Massimo Nov 02 '12 at 18:49
  • @Massimo Use a class which will be value in the map which contains `AutomicBoolean` which will be marked if some one is working on it then you will not remove it nor return value. – Amit Deshpande Nov 02 '12 at 18:53
  • 1
    Then you are talking of my solution? The problem of that is: how can I avoid this map to grow indefinitely? Someone should remove useless elements... – Massimo Nov 02 '12 at 18:56
  • @Massimo No It will not. Since once you set flag to false you can remove that element at that instance only so it will not grow indefinately – Amit Deshpande Nov 02 '12 at 18:58
  • The problem is that I need to do these operation on Android, which does not have AutomicBoolean... :( – Massimo Nov 02 '12 at 19:19
  • @Massimo Sorry for the spell its AtomicBoolean here it link to android java doc http://developer.android.com/reference/java/util/concurrent/atomic/AtomicBoolean.html – Amit Deshpande Nov 02 '12 at 19:34
  • Ok :) I found "AutomicBoolean" and it seems to do something similar but it is in a third party library... let's see Atomic one. – Massimo Nov 02 '12 at 20:05
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/18992/discussion-between-amitd-and-massimo) – Amit Deshpande Nov 02 '12 at 20:06
  • Its there is android from API level 1 so you don;t need third party library. Notice the spell mistake `AtomicBoolean` not `AutomicBoolean` – Amit Deshpande Nov 02 '12 at 20:08