222. ReentrantLock

The Lock interface contains a set of locking operations that can be explicitly used to fine-tune the locking process (it provides more control than intrinsic locking). Among them, we have polled, unconditional, timed, and interruptible lock acquisition. Basically, Lock exposes the futures of the synchronized keyword with additional capabilities. The Lock interface is shown in the following code:

public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException;
void unlock();
Condition newCondition();
}

One of the implementations of Lock is ReentrantLock. A reentrant lock acts as follows: when the thread enters for the first time into the lock, a hold count is set to one. Before unlocking, the thread can re-enter the lock causing the hold count to be incremented by one for each entry. Each unlock request decrements the hold count by one, and, when the hold count is zero, the locked resource is opened.

Having the same coordinates as the synchronized keyword, ReentrantLock follows the following idiom of implementation:

Lock / ReentrantLock lock = new ReentrantLock();
...
lock.lock();

try {
...
} finally {
lock.unlock();
}
In the case of non-fair locks, the order in which threads are granted access is unspecified. If the lock should be fair (give precedence to the thread that has been waiting for the longest) then use the ReentrantLock​(boolean fair) constructor.

Summing up integers from 1 to 1,000,000 via ReentrantLock can be accomplished as follows:

public class CounterWithLock {

private static final Lock lock = new ReentrantLock();

private static int count;

public void counter() {
lock.lock();

try {
count++;
} finally {
lock.unlock();
}
}
}

And, let's use it via several threads:

CounterWithLock counterWithLock = new CounterWithLock();
Runnable task = () -> {
counterWithLock.counter();
};

ExecutorService executor = Executors.newFixedThreadPool(8);
for (int i = 0; i < 1 _000_000; i++) {
executor.execute(task);
}

Done!

As a bonus, the following code represents an idiom for resolving problems based on ReentrantLock.lockInterruptibly(). The code bundled to this book comes with an example of using lockInterruptibly():

Lock / ReentrantLock lock = new ReentrantLock();
public void execute() throws InterruptedException {
lock.lockInterruptibly();

try {
// do something
} finally {
lock.unlock();
}
}

If the thread holding this lock is interrupted then InterruptedException is thrown. Using lock() instead of lockInterruptibly() will not be receptive to interruption.

In addition, the following code represents an idiom for using ReentrantLock.tryLock(long timeout, TimeUnit unit) throws InterruptedException. The code bundled to this book comes with an example as well:

Lock / ReentrantLock lock = new ReentrantLock();

public boolean execute() throws InterruptedException {

if (!lock.tryLock(n, TimeUnit.SECONDS)) {
return false;
}

try {
// do something
} finally {
lock.unlock();
}

return true;
}

Note that tryLock() tries to acquire the lock for the specified time. If this time elapses, then the thread will not acquire the lock. It doesn't retry automatically. If the thread is interrupted during attempting to acquire the lock, then InterruptedException will be thrown.

Finally, the code bundled to this book comes with an example of using ReentrantLock.newCondition(). The idiom is in the next screenshot:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.67.27