Skip navigation

Jive Developers

1 Post authored by: jonathan.colt

Writing thread safe code is hard. As developers we really need to see the whole context all at once to be able to reason through all the potential execution orderings. The usage of locks are typically spread throughout a given class or classes which makes holding the whole context in your head difficult. I am going to present some guidelines and solutions to common Java locking problems that are make the code more readable.

Context for the following guidelines:


public class Foo {

    // Method level locks should be avoided
    synchronized void setFoo(int x, int y) {
        // do some field assignments
    }

    private final Object lock = new Object(); // Lock Object.
    void setFoo(int x, int y) {
        synchronized(lock) { // Better than method level locks.
            // do some field assignments
            instance.doSomething(x,y); // Should be avoided
        }
    }
   
    ReetrantLock reetrantLock;
    void setFoo(int x, int y) {
        try {
            reetrantLock.lock();
            // do some field assignments
        } finally {
            reetrantLock.unlock();
        }
    }
}


Guidelines:

  • Avoid using synchronized on methods. Instead use a lock object.
  • All lock objects must be final. No exceptions!
  • Try to avoid calling methods within a given synchronized block. If you have to then make them final or private. This will reduce the chance of unintentional nesting of locks.
  • Stick with old school synchronized unless you really need one of the features of the java.util.concurrent.locks. In general needing a feature of java.util.concurrent.locks is a code smell that your solution is overly complex. In general an old school synchronized block with a lock object is more readable. Yes reetrant locks can be faster when there is high contention however I suggest reading When to use reetrant locks.
  • Locks can frequently be avoided by using classes from java.util.concurrent and java.util.concurrent.atomic.


One of the key pivot points for when to use a reetrant lock is high contention. There are many times when you can and should partition your problem across a collection of locks.

 

Here is a pathological example for a method that would have very high lock contention.

 

public class Foo {

    private final Object lock = new Object(); // Lock Object.
    void updateUsersGeoLocation(String tenantId,long userId, double lat, double lon) {
        synchronized(lock) {
            Tenant tenant = db.getTenant(tenantId);
            User user = tenant.getUser(userId);
            user.setLat(lat);
            user.setLon(lon)
        }
    }
   
    LatLon getUsersGeoLocation(String tenantId, long userId) {
        synchronized (lock) {
            Tenant tenant = db.getTenant(tenantId);
            User user = tenant.getUser(userId);
            return new LatLon(user.getLat(), user.getLon());
        }
    }
}


In a perfect world we would have access to the User class and could put the locking there. For this example the User class is a third party class that cannot be changed.

 

So how can this contention be reduced? There are two levels of lookups. The first gets the Tenant instance for a given tenantId and the second gets a User instance for a given userId. If only we had a way to have a lock per tenant and a lock per user. If you look around the web you will find a lot of very complex solutions to this problem. Here is a very simple solution. The LocksProvider class is a nice way to create a bag of locks that can decongest a hot lock. It relies on using a key that has a good hashcode distribution. You don’t create a lock for every user and every tenantId. Instead you create N times the number of threads you know will be changing the Users state. The advantage to this solution is you can increase the number of locks to decrease the odds of contention on a given lock. You don’t have to solve any of the problems created by using the actual tenantId or userId as the lock. This solution to locking is sometimes called striped locking.


public class LocksProvider<K> {

    private final Object[] locks;

    public LocksProvider(int numLocks) {
        locks = new Object[numLocks];
        for (int i = 0; i < numLocks; i++) {
            locks[i] = new Object();
        }
    }

    public Object lock(K key) {
        return locks[Math.abs(key.hashCode() % locks.length)];
    }
}


Using the LocksProvider class we can rewrite the pathological example to look something like this.


public class Foo {

    private final LocksProvider<String> tenantLocks = new LocksProvider<>(10);
    private final LocksProvider<Long> userLocks = new LocksProvider<>(10);

    void updateUsersGeoLocation(String tenantId, long userId, double lat, double lon) {
        Tenant tenant = getTenant(tenantId);
        synchronized (userLocks.lock(userId)) {
            User user = tenant.getUser(userId);
            user.setLat(lat);
            user.setLong(lon)
        }
    }

    LatLon getUsersGeoLocation(String tenantId, long userId) {
        Tenant tenant = getTenant(tenantId);
        synchronized (userLocks.lock(userId)) {
            User user = tenant.getUser(userId);
            return new LatLon(user.getLat(), user.getLon());
        }
    }
    private Tenant getTenant(String tenantId) {
        synchronized (tenantLocks.lock(tenantId)) {
            return db.getTenant(tenantId);
        }
    }
}


We now have an implementation that will have much less lock contention. If your use case still proves to have very high contention and you cannot control the hash distribution of your key then you can replace the ”private final Object[] locks;” in LocksProvider with “private final ReetrantLock[] locks;” to leverage the performance or functionality of ReentrantLocks.


If you made it this far you clearly have an interest in locking. For further reading I suggest you check out http://en.wikipedia.org/wiki/Lamport's_bakery_algorithm.

Filter Blog