I am developing a pool mechanism. Objects in the pool will be used by different threads. Thus I need to guarantee that there will be a happens-before
relation when accessing non-final attributes of these objects.
As a given object will be accessed by only one thread at a time, I do not need to guarantee any atomicity. What is more optimized when only happens-before
needs to be achieved? 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
?
(I am not asking this question for attributes of the pool itself, that obviously needs to provide atomicity; I am only asking this question for the attributes of objects obtained from the pool)
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.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.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: