Vikas Bansal
Vikas Bansal

Reputation: 147

Is there a way to synchronize on String variable in java apart from using intern method

Intern method will keep adding the strings into the pool which is a bad idea for long term process.Exact scenario is like this :

// rowKey variable defined below is the value of a row key extracted from a 
// table of the db. There can be many rowKey values. (>100000).
String rowKey = "rowId_123123"; 
/* Once rowKey is extracted, then if another thread extracts the same 
   row from the table, then it should be put to wait. Object locking will
   put all the threads to wait irrespective of the rowKey value which will 
   hinder performance. Interning the string will put it into the pool till 
   jvm execution.
*/ 

Is there a way to achieve this without interning the string and synchronizing only on the string. Or if there is a way to convert a string into an object which can be visible to all the threads?

Upvotes: 2

Views: 1847

Answers (2)

Emily Mabrey
Emily Mabrey

Reputation: 1558

Use Guava Striped Class

In the past I have had success using the Guava Striped class for this type of Object to Lock mapping operation. For in-depth details on the Striped class please see the Guava wiki or the Striped JavaDoc. Essentially, using a Striped<Lock> instance to index Lock values via String keys is a thread-safe way to generate Lock instances that minimizes memory footprint while maximizing concurrency. If you are avoiding 3rd party libraries you could instead implement your own Map<Object, Lock> wrapper (perhaps mixing in WeakReference) to roll your own less full-featured version of the Striped class (be sure to directly compare hashcodes instead of relying on the == operator or the equals methods!). In either case, the Striped class is a good place to start learning about how to implement your lock generation/retrieval.


Example

Note that for a specific use case, like for achieving atomic operations for database rows, you might want to use a ReadWriteLock instead of a simple/basic Lock (using Semaphore is also possible). Also, please note that we don't need to intern the String object given to Striped.get() because the Striped class compares Object's for equality using hashcodes and the String class makes special guarantees about hashcodes between character equivalent Strings always being equal. This example does use an interned String (literal Strings are automatically interned) but the Striped class works perfectly well with a dynamically generated String which is not interned.

final Striped<Lock> stripedLocks = Striped.lock(10);

final String rowID = "rowLock123";

final Lock rowLock = stripedLocks.get(rowID);

try{
     rowLock.lock();//Could also use tryLock or lockInterruptibly methods

     //... we are successfully in the fenced/locked code block now
     //... put your concurrency sensitive code here

}finally{
     rowLock.unlock();
}

Correction

Don't use synchronized on the returned Lock object obtained from Striped.get(String)!

I am adding this explicit warning to not use the returned Lock object as a synchronized block's monitor because someone edited my answer to include an example that incorrectly used the returned Lock as a monitor object. For reference, this is what that would look like in the above example:

//DO NOT USE THE RETURNED LOCK LIKE THIS

final Lock rowLock = stripedLocks.get(rowID);

synchronized(rowLock){
    //...oh no
}

You should not use synchronized on a Lock because that defeats the entire purpose of using Lock! The Lock class is intended as a replacement for the use of synchronized blocks. Using them together sacrifices the benefits of Lock while still having the headaches and gotchas of synchronized.

Upvotes: 3

Boann
Boann

Reputation: 50031

You can create your own object interning pool with weak referencing, so that it will expire unused keys.

Something like this should do it.. I think..:

final class RowLock {
    public final int rowId;

    private RowLock(int rowId) {
        this.rowId = rowId;
    }

    public static synchronized RowLock getLockObject(int rowId) {
        RowLock r = new RowLock(rowId);
        WeakReference<RowLock> rInternedRef = pool.get(r);
        if (rInternedRef != null) {
            RowLock rInterned = rInternedRef.get();
            if (rInterned != null) {
                return rInterned;
            }
        }
        pool.put(r, new WeakReference<>(r));
        return r;
    }

    private static final WeakHashMap<RowLock,WeakReference<RowLock>> pool =
        new WeakHashMap<>();

    @Override
    public int hashCode() {
        return rowId;
    }

    @Override
    public boolean equals(Object obj) {
        return obj == this || (obj instanceof RowLock && ((RowLock)obj).rowId == rowId);
    }
}

If your row IDs are not numeric, you can use a String rowId instead and it will work just as well.

When you want to lock on a row, call getLockObject and then synchronize on the returned object, which will have been "interned" via the map, so that all threads will get the same object. Once nothing is strongly referring to a RowLock instance any more, it will be eligible for garbage collection, because its interning pool only refers to it with weak references.

Upvotes: 1

Related Questions