javasynchronized

Synchronized lock by particular ID


I have a REST API which has one method M which does something.
Of course it's called by multiple threads sometimes simultaneously.
This method M has an input String businessID
(which comes from the payload of the caller/client).

Now... I want to protect one particular section from method M's body against simultaneous execution by multiple threads. But I want to defend it only if I have simultaneous executions by two threads T1 and T2 for the same businessID value. So after some thinking I decided to go with this approach.

public M(){ 
    
    // non-critical work 1 
    
    String bid = businessID.intern();
    synchronized (bid){
        // do some critical work here 
    }
    
    // non-critical work 2 
    
}

That means I intend to use the interned version of the String businessID as a lock to my critical section of code.

Is this going to work as intended? I think so... but I want to be absolutely sure.

Also, does anyone have any alternative ideas how to implement this? I wonder if there's some ready-made solution, like an idiomatic way of doing this in Java, without having to implement my own cache, my own eviction mechanism, etc. etc.

Note that delays caused by this synchronization are not worrying me. It is very rare scenario two threads to call the method M with the same business ID at the same tome (happens only once or twice per day). Also the critical section takes no more than 1-2 secs to complete execution. So delays caused by threads waiting for obtaining the lock, this is not worrying me here.


Solution

  • It seems like a bad idea because:

    1. String#intern() is a native method and it uses native Hash Table which apparently is much slower than a typical ConcurrentHashMap.
    2. You probably don't want the pool of strings to grow indefinitely. How would you invalidate entries from there?

    I think using your own map of strings will be the preferred way. Maybe Guava's cache could be leveraged because you need to evict items from that map eventually. But this needs further research.

    Leasing a lock

    Another option is to have a set of predefined lock objects. E.g. a HashMap of size 513. Then to acquire a lock use bid.hashCode() mod 513:

    int hash = Math.abs(bid.hashCode() % 513);
    Object lock = locks.get(hash);
    synchronized(lock) {...}
    

    This will occasionally lock unrelated transactions, but at least you don't have to bother with eviction.

    SELECT FOR UPDATE

    A more idiomatic way of doing this is through a lock in the database (if you have one):

    select * from mytable where id = 1 for update