Better synchronization in Java
A few tips for better thread synchronization in Java from Java Concurrency in Practice:
1. Narrow lock scope
Lock only the portion of code that really needs to be locked. This can dramatically reduce the time threads wait to acquire the lock. Consider the following method:
public synchronized boolean addItem(Item item) { if (validate(item)) { return itemsArray.add(item); } return false; }
The method level lock prevents two threads from concurrently running validate(item), which need not be synchronized. To get the full benefit of multi-threading we change the method like this:
public boolean addItem(Item item) { if (validate(item)) { synchronized(this) { return itemsArray.add(item); } } return false; }
2. Reduce lock granularity
If there are two objects whose concurrent modification are sure not to create havoc, synchronize them with dedicated locks. For instance, here the thread that adds an Item will have to wait until the thread that adds an Order is finished :
public boolean addItem(Item item) { if (validate(item)) { synchronized(this) { return itemsArray.add(item); } } return false; } public boolean addOrder(Order order) { if (validate(order)) { synchronized(this) { return ordersArray.add(order); } } return false; }
The performance can be improved by increasing the granularity of the locks:
private Object itemLock = new Object(); private Object orderLock = new Object(); public boolean addItem(Item item) { if (validate(item)) { synchronized(itemLock) { return itemsArray.add(item); } } return false; } public boolean addOrder(Order order) { if (validate(order)) { synchronized(orderLock) { return ordersArray.add(order); } } return false; }
3. Strip locks, if possible
A single collection can be partition locked, like we see in the implementation of this Map:
public class HighlyResponsiveMap { private static final int N_LOCKS = 16; private final Object locks[] = new Object[N_LOCKS]; private final Node buckets[]; public HighlyResponsiveMap(int numBuckets) { buckets = new Node[numBuckets]; for (int i = 0; i < N_LOCKS; ++i) { locks[i] = new Object(); } } public Object get(Object key) { int hash = key.hashCode(); synchronized(locks[hash % N_LOCKS]) { // Find node from the bucket and return its value. } return null; } }
4. Avoid object pools
In days of yore, when allocating memory for a new Object was really slow, people used to depend on object pools. These days creating a new object in Java often outperforms C’s malloc. Synchronizing an object pool can be expensive than creating a new Object when needed. If you are using Java 5 or later, avoid object pools.