To use a synchronized block on a final attribute each time I read or
modify one of the non-final attribute? Or to use concurrent classes,
such as a ConcurrentMap?
Synchronization is built around an internal entity known as the intrinsic lock or monitor lock. Every object has an intrinsic lock associated with it. By convention, a thread that needs exclusive and consistent access to an object's fields has to acquire the object's intrinsic lock before accessing them, and then release the intrinsic lock when it's done with them.
An implementation of ConcurrentMap
i.e., ConcurrentHashMap
uses reentrant lock which is mutual exclusive lock, Lock is acquired by lock()
method and held by Thread until a call to unlock()
method. Though, ReentrantLock
provides same visibility and orderings guaranteed as implicit lock, acquired by synchronized keyword
, it provides more functionality and differ in certain aspect:
ReentrantLock
can be made fair by specifying fairness property
to provides lock to longest waiting thread, in case of contention.
- provides convenient
tryLock()
method to acquires lock, only if its available or not held by any other thread, reducing blocking of thread waiting for lock. tryLock()
with timeout can be used to timeout if lock is not available in certain time period.
- In case of
synchronized keyword
, a thread can be blocked waiting for lock, for an indefinite period of time and there was no way to control that. ReentrantLock
provides a method called lockInterruptibly()
, which can be used to interrupt thread when it is waiting for lock.
An example of use reentrant lock
can be shown from ConcurrentHashMap
's inner replace(K key, V oldValue, V newValue)
function implementaion:
boolean replace(K key, int hash, V oldValue, V newValue) {
// called by replace(K key, V oldValue, V newValue)
lock(); // acquire the lock
try {
HashEntry<K,V> e = getFirst(hash);
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
boolean replaced = false;
if (e != null && oldValue.equals(e.value)) {
replaced = true;
e.value = newValue;
}
return replaced;
} finally {
unlock(); // unlock
}
}
There are other functions like put()
, writeObject(java.io.ObjectOutputStream)
, etc. are also implemented using reentrant synchronization using the ReentrantLock
, without which, synchronized code would have to take many additional precautions to avoid having a thread cause itself to block. That is why I think for your case ConcurentMap
is preferable.
Reference:
- Intrinsic Locks and Synchronization
- ReentrantLock Class
- Difference between synchronized vs ReentrantLock
- ConcurrentHashMap