“This is the 24th day of my participation in the First Challenge 2022. For details: First Challenge 2022”
ReentrantLock
1.1 the characteristics of
ReentrantLock has the following features:
- reentrant
- interruptible
- You can set the timeout period
- Can be set to fair lock
- Conditional variable support
The realization principle of its characteristics will be explained in detail later.
1.2 Code Structure
Its code structure is shown as follows:
There are three inner classes: Sync, FairSync, and NonfairSync.
Sync inherited from AbstractQueuedSynchronizer.
AbstractQueuedSynchronizer Node and ConditionObject of two inner classes.
The literal meaning of the above class should give you a sense of where the aforementioned features are implemented.
Two, principle analysis
2.1 reentrant
Reentrant means that if the same thread first acquires the lock, it has the right to acquire it again because it is the owner of the lock. If it is a non-reentrant lock, then the second attempt to acquire it will itself be blocked by the lock.
Synchronized is also reentrant.
Examples of reentrant applications are as follows:
public class Test {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1(a) {
lock.lock();
try {
System.out.println("method1");
method2();
} finally {
lock.unlock();
System.out.println("method1 unlock"); }}public static void method2(a) {
lock.lock();
try {
System.out.println("method2");
method3();
} finally {
lock.unlock();
System.out.println("method2 unlock"); }}public static void method3(a) {
lock.lock();
try {
System.out.println("method3");
} finally {
lock.unlock();
System.out.println("method3 unlock"); }}}Copy the code
Results:
method1
method2
method3
method3 unlock
method2 unlock
method1 unlock
Copy the code
Note that lock.unlock() must be on the first line of the finally block.
-
Source code analysis
Step by step, using the previous code:
Acquiring a lock
public void lock(a) { sync.lock(); } Copy the code
Sync’s lock method has two implementation classes, fair and unfair:
An unfair lock is used here because NonfairLock is used by default when ReetrantLock is initialized:
public ReentrantLock(a) { sync = new NonfairSync(); } Copy the code
To continue source tracking, the lock() method in an unfair lock:
final void lock(a) { // Use the spin lock here to determine whether the current thread holds the lock, and replace the value with 1 if it is 0 if (compareAndSetState(0.1)) // Set the lock to be exclusive to this thread setExclusiveOwnerThread(Thread.currentThread()); else // The comparison is not valid, try to obtain the lock acquire(1); } Copy the code
When a thread reenters or another thread attempts to acquire the lock, acquire(1) : setExclusiveOwnerThread () : setExclusiveOwnerThread () : setExclusiveOwnerThread () : setExclusiveOwnerThread () : setExclusiveOwnerThread ()
public final void acquire(int arg) { // Try to get the lock, if(! tryAcquire(arg) &&// With the short-circuit logic operator, when the fetch fails, the thread is added to the wait queue acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // Interrupt the current thread, actually set the interrupt flag selfInterrupt(); } Copy the code
Let’s focus on tryAcquire and see how to implement lock reentrant. Skip the intermediate process and look directly at the following code:
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); // Get the current thread synchronization status. State is volatile int c = getState(); // Indicates that no thread holds the lock if (c == 0) { // Spin lock, same process as before if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; }}// The current thread is the thread holding the lock else if (current == getExclusiveOwnerThread()) { // Add 1 to the current state int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); // Set the status value setState(nextc); return true; } return false; } Copy the code
Unlock: Unlock unlock: Unlock unlock
protected final boolean tryRelease(int releases) { // Get the current state minus 1 int c = getState() - releases; if(Thread.currentThread() ! = getExclusiveOwnerThread())throw new IllegalMonitorStateException(); boolean free = false; // The lock can be reentrant multiple times. The lock can only be released when the state is reduced to 0. if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } Copy the code
The principle of lock reentrant is fairly simple, so that’s it.
2.2 can disrupt
In addition to the regular lock() method, ReetrantLock provides a interruptible method, lockInterruptibly(), that will throw an exception if the thread holding the lock is interrupted while obtaining the lock:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
System.out.println("Start...");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("An interruption in the process of waiting for a lock.");
return;
}
try {
System.out.println("Got the lock.");
} finally{ lock.unlock(); }},"t1");
lock.lock();
System.out.println("Got the lock.");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
System.out.println("Executive interruption");
} finally{ lock.unlock(); }}}Copy the code
Results:
Got the lock start... In the process of implementing interrupt lock was broken. Java lang. InterruptedException ats java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at com.cloud.bssp.juc.reetrantlock.InterruptTest.lambda$main$0(InterruptTest.java:19) at java.lang.Thread.run(Thread.java:748)Copy the code
If the lock() method is used, even if the thread is interrupted, the lock can still be acquired and no exceptions will be thrown.
2.3 You can set the timeout period
ReetrantLock provides two methods to acquire the lock and return quickly, without waiting forever and returning immediately regardless of success or failure:
-
TryLock () When the lock is not held, the tryLock() method will immediately acquire the lock even if it is a fair lock, which is unfair but useful.
-
TryLock (long timeout, TimeUnit Unit) allows you to set a timeout period. Unlike tryLock, this method attempts to acquire the lock at the end of the timeout period. If it succeeds, it holds the lock and returns immediately. Cannot acquire a lock, in contrast to tryLock.
TryLock () test:
public class TryLockTest {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
if(! lock.tryLock()) { System.out.println("Lock acquisition failed");
return;
} else {
try {
System.out.println("Lock obtained successfully");
} finally{ lock.unlock(); }}}); lock.lock();try {
t1.start();
TimeUnit.SECONDS.sleep(1);
} finally{ lock.unlock(); }}}Copy the code
Results:
Failed to obtain the lockCopy the code
Timed tryLock() looks like this:
public class TryLockTimeTest {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
if(! lock.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Waiting one second to acquire lock failed");
return;
} else {
try {
System.out.println("Wait one second to obtain lock success");
} finally{ lock.unlock(); }}}catch(InterruptedException e) { e.printStackTrace(); }}); lock.lock();try {
t1.start();
System.out.println("Wait two seconds.");
TimeUnit.SECONDS.sleep(2);
} finally{ lock.unlock(); }}}Copy the code
Results:
Wait two seconds Wait one second failed to obtain the lockCopy the code
2.4 Setting a Fair lock
As mentioned earlier, ReentrantLock is unfair by default. Unfair is used because fair locks are generally unnecessary and reduce concurrency.
Create a fair lock as follows:
ReentrantLock lock = new ReentrantLock(true);
Copy the code
Tracker constructor:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code
Focus on its fair lock implementation:
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock(a) {
// Inherit from AQS method, internal first call tryAcquire to obtain the lock, if the acquisition fails, add downtown to wait queue
acquire(1);
}
/** * Fair lock version of tryAcquire */
protected final boolean tryAcquire(int acquires) {
// Get the current thread
final Thread current = Thread.currentThread();
// Get the lock status
int c = getState();
// 0 indicates that the lock is not held
if (c == 0) {
// Check whether there are nodes waiting in the current wait queue
if(! hasQueuedPredecessors() &&// Compare and replace states
compareAndSetState(0, acquires)) {
// Set the current thread to an exclusive thread
setExclusiveOwnerThread(current);
return true; }}else if (current == getExclusiveOwnerThread()) {
// Lock reentrant
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false; }}Copy the code
2.5 Conditional Variables
ReentrantLock supports multiple condition variables.
What to make of the above sentence? When we learned synchronized, we introduced its wait method. When a thread calls its wait method, it changes from the owner of the thread to the wait state and is added to the WaitSet of Monitor. When other threads call wait again, it is still added to the wait state. It’s like a common room.
ReentrantLock’s multiple condition variables are like multiple lounges.
ReentrantLock returns conditional variables using await()/signal() methods and conditionObject queues.
public class ConditionTest {
static ReentrantLock lock = new ReentrantLock();
static Condition Tom = lock.newCondition();
static Condition Jerry = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
lock.lock();
Tom.await();
System.out.println("Eat the fish.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
Jerry.await();
System.out.println("Got the cheese.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
TimeUnit.SECONDS.sleep(1);
try {
lock.lock();
System.out.println("Here comes the fish.");
Tom.signal();
} finally {
lock.unlock();
}
TimeUnit.SECONDS.sleep(1);
try {
lock.lock();
System.out.println("Here comes the cheese");
Jerry.signal();
} finally{ lock.unlock(); }}}Copy the code
Results:
Fish came, fish came, cheese came, cheese cameCopy the code
As shown above, there are several key points:
- Need to get lock before await
- The lock is released after await
- Call signal to wake up the thread, but also need to acquire the lock, otherwise an error will be reported. The awakened thread races again and continues execution from behind await.
- Remember unlock.
Now let’s focus on how to do that? Only the key methods are covered
Await method:
public final void await(a) throws InterruptedException {
// If the thread status is interrupted, an exception is thrown
if (Thread.interrupted())
throw new InterruptedException();
// Add the current thread to the conditional wait queue
Node node = addConditionWaiter();
// Release the lock
int savedState = fullyRelease(node);
int interruptMode = 0;
// When the node is not in a synchronous wait queue
while(! isOnSyncQueue(node)) {// Block the current thread
LockSupport.park(this);
if((interruptMode = checkInterruptWhileWaiting(node)) ! =0)
break;
}
// Get the wait queue lock without throwing an interrupt exception
if(acquireQueued(node, savedState) && interruptMode ! = THROW_IE)// Reset the interrupt flag
interruptMode = REINTERRUPT;
// Clear the cancelled node
if(node.nextWaiter ! =null)
unlinkCancelledWaiters();
// If the interrupt mode is not 0, the state determines whether to throw an exception, interrupt the thread or do nothing at all
if(interruptMode ! =0)
reportInterruptAfterWait(interruptMode);
}
Copy the code
AddConditionWaiter method:
private Node addConditionWaiter(a) {
// The last person waiting in conditionObject
Node t = lastWaiter;
// If the last wait is cancelled, clear it (not null, and the state is not waiting)
if(t ! =null&& t.waitStatus ! = Node.CONDITION) { unlinkCancelledWaiters(); t = lastWaiter; }// The node that sets the current thread to wait
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
Copy the code
Method of signal:
public final void signal(a) {
// Determine if the thread holds the lock, and raise an exception if it does not
if(! isHeldExclusively())throw new IllegalMonitorStateException();
Node first = firstWaiter;
// If the first wait of the conditional queue is not null, signal wake up is performed
if(first ! =null)
doSignal(first);
}
Copy the code
DoSignal method
private void doSignal(Node first) {
do{if the next wait for the first node isnull
if ( (firstWaiter = first.nextWaiter) = =null)
// The last person waiting for the conditional queue is set to NULL
lastWaiter = null;
first.nextWaiter = null;
} while(! transferForSignal(first) && (first = firstWaiter) ! =null);
}
Copy the code
TransferForSignal method
final boolean transferForSignal(Node node) {
// Check whether the node status is condition, if it is 0, otherwise return false
if(! compareAndSetWaitStatus(node, Node.CONDITION,0))
return false;
/* * Add the node to the synchronous wait queue */
Node p = enq(node);
int ws = p.waitStatus;
// Here the wait state is 0, compare and replace the status with SIGNAL
if (ws > 0| |! compareAndSetWaitStatus(p, ws, Node.SIGNAL))// Unblock the thread
LockSupport.unpark(node.thread);
return true;
}
Copy the code
About ReentrantLock briefly introduced these, in fact, should first learn AQS, otherwise may not understand the source code.