Let's say I have two critial resources, foo and bar. I protect them with some ReentrantReadWriteLock
s
ReentrantReadWriteLock foo = new RRWL() ...
ReentrantReadWriteLock bar = new RRWL() ...
Most operations only use foo OR bar, but some of them happen to use both. Now when using a single lock, you can't just do this:
void foo() {
foo.writeLock().lock();
privateWorkOnFoo();
foo.writeLock().unlock();
}
If an exception is thrown, your foo will become forever locked. Instead you wrap it, like
void foo() {
try {
foo.writeLock().lock();
privateWorkOnFoo();
} finally { foo.writeLock().unlock(); }
}
But what if I need to work on both? Is it safe to put them in one block?
try {
foo.writeLock().lock();
bar.writeLock().lock();
magic();
} finally {
bar.writeLock().unlock();
foo.writeLock().unlock();
}
Or is it necessary to give each lock its own block:
try {
foo.writeLock().lock();
try {
bar.writeLock().lock();
magic();
} finally {
bar.writeLock().unlock();
}
} finally {
foo.writeLock().unlock();
}
I can't have been the first person to have hard to investigate this before... I know option 2 there is "bulletproof" but it's also a significant amount more maintenance. Is option 1 acceptable?
This lock supports a maximum of 65535 recursive write locks and 65535 read locks. Attempts to exceed these limits result in Error throws from locking methods.
Each object in Java is associated with a monitor, which a thread can lock or unlock. Only one thread at a time may hold a lock on a monitor. Any other threads attempting to lock that monitor are blocked until they can obtain a lock on that monitor.
ReentrantReadWriteLock class of Java is an implementation of ReadWriteLock, that also supports ReentrantLock functionality. The ReadWriteLock is a pair of associated locks, one for read-only operations and one for writing.
Simply put, a lock is a more flexible and sophisticated thread synchronization mechanism than the standard synchronized block. The Lock interface has been around since Java 1.5. It's defined inside the java. util. concurrent.
Option 1 is fine. It's known as the two lock variant. If you look at LinkedBlockingQueue operations such as remove, it locks the putLock as well as the takeLock. Here's a sample of what the JDK does:
public boolean remove(Object o) {
if (o == null) return false;
fullyLock();
try
{
// ...
}
finally {
fullyUnlock();
}
}
/**
* Lock to prevent both puts and takes.
*/
void fullyLock() {
putLock.lock();
takeLock.lock();
}
/**
* Unlock to allow both puts and takes.
*/
void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
Option 1 is actually safer than Option 2, since if an exception is thrown in option 2, the second lock (foo
) won't be unlocked: the unlocking is not in a finally block.
Also, be very careful when manipulating two locks, because there's a good chance of deadlock if one thread locks foo
then bar
, and another thread locks bar
then foo
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With