Consider the following code:
public class MyDataStructure {
int size;
final ReentrantLock lock = new ReentrantLock();
public void update() {
lock.lock();
try {
// do coll stuff
size ;
} finally {
lock.unlock();
}
}
public int size() {
return size;
}
}
As far as I understand, ReentrantLock
imposes an happens-before relationship so size
needs to affect the main memory and the cache (of other threads) with the new value. Hence, no need to use volatile
for size
. Also, for the same reason the method size()
can simply return size
.
Is my understanding correct?
CodePudding user response:
No.
Other threads may see a stale (old) value of size.
When other threads execute size()
there is nothing there instructing it to ensure it's not reading an old value, such as a locally cached value in another cpu cache etc. Also worth mentioning is hoisting by the jvm.
AN example, if you have a loop calling size()
in one thread, it may never exit if update()
is not called in the loop, or size changed, and called/changed from other threads only.
while (size() == 0) {
...
}
The jit compiler, (at least the modern part what used to be called the c2/server compiler) could happily optimize away (hoist) the size variable checks as it sees that it never changes in the loop.
CodePudding user response:
As far as I understand, ReentrantLock imposes an happens-before relationship...
Yes. When some thread W (W for "writer") unlocks a lock, that "happens before" some other thread R (for "reader") subsequently locks the same lock.
In order to take advantage of that relationship, you have to lock the same lock in both methods:
public class MyDataStructure {
int size;
final ReentrantLock lock = new ReentrantLock();
public void update() {
lock.lock();
try {
size ; // happens before unlock() because things that happen
// in one thread all happen in _program order._
} finally {
lock.unlock(); // Happens before a pending lock.lock() call in some
// other thread.
}
}
public int size() {
lock.lock(); // Happens before return size because...
// ...program order.
try {
return size;
} finally {
lock.unlock();
}
}
}