preface
Interview question: Is synchronized reentrant?
Synchronized is a reentrant lock. So does ReentrantLock.
What is a reentrant lock?
Let’s start with a wikipedia definition of what a reentrant lock is.
A program or subroutine is reentrant if it can “be interrupted at any time and the operating system schedules the execution of another piece of code that calls the subroutine without error.” That is, while the subroutine is running, the thread of execution can come in and execute it again, still getting the results expected at design time. Unlike thread-safety, where multiple threads execute concurrently, reentrant emphasizes that it is still safe to re-enter the same subroutine while executing on a single thread.
In plain English: when a thread requests an object lock held by another thread, the thread blocks. When a thread requests an object lock held by itself, the request succeeds if the lock is a reentrant lock, and otherwise blocks.
To put it another way: reentrant means that a thread that has acquired a lock can acquire it again without a deadlock.
2. Write your own code to verify reentrant and non-reentrant
We start a thread T1 and call the addOne() method to perform the increment. In the addOne method t1 acquires the RTL lock and then calls get(), where it requests the TRL lock again.
Since value=1 is eventually printed, t1 is not blocked on the second lock acquisition. Note ReentrantLock is a ReentrantLock.
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantTest { private final Lock rtl = new ReentrantLock(); int value = 0; public static void main(String[] args) throws InterruptedException { ReentrantTest test = new ReentrantTest(); Thread t1 = new Thread(() -> test.addOne()); t1.start(); // The main thread waits for the T1 thread to finish executing t1.join(); System.out.println(test.value); } public int get() {rtl.lock(); try { return value; } finally {// rtl.unlock(); }} public void addOne() {rtl.lock(); try { value = 1 + get(); } finally {// rtl.unlock(); }}}Copy the code
Synchronized can also print the value of value. Proof that synchronized is also reentrant.
public class ReentrantTest { private final Object object = new Object(); int value = 0; public static void main(String[] args) throws InterruptedException { ReentrantTest test = new ReentrantTest(); Thread t1 = new Thread(() -> test.addOne()); t1.start(); t1.join(); System.out.println(test.value); } public int get() {synchronized (object) {return value; }} public void addOne() {synchronized (object) {value = 1 + get(); }}}Copy the code
3. How do you implement a reentrant and non-reentrant lock
Not reentrant:
public class Lock{ private boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); }}Copy the code
Reentrant:
public class Lock{ boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(isLocked && lockedBy ! = callingThread){ wait(); } isLocked = true; lockedCount++; lockedBy = callingThread; } public synchronized void unlock(){ if(Thread.curentThread() == this.lockedBy){ lockedCount--; if(lockedCount == 0){ isLocked = false; notify(); }}}}Copy the code
From the code implementation, reentrant lock added two states, the counter and locked the thread lock, implement and non-reentrant basically the same, if different threads to come in, there is no question of the lock, but if the recursive calculation, if the lock, non-reentrant lock deadlock problem will occur.
4. How does ReentrantLock implement reentrant
ReentrantLock supports fair and unfair lock creation. By default, an unfair lock is created.
Take a look at how it is constructed:
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Copy the code
Take a look at unfair locks, which inherit from the abstract class Sync:
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }}Copy the code
Take a look at fair lock, which also inherits from the abstract class Sync:
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
Copy the code
NonfairSync, FairSync, and the abstract class Sync are all internal classes to ReentrantLock.
The definition of the Sync, it is inheriting AbstractQueuedSynchronizer AbstractQueuedSynchronizer is we often say AQS (later I will arrange a)
abstract static class Sync extends AbstractQueuedSynchronizer {
}
Copy the code
Ok, now that the inheritance relationship is clear, how does ReentrantLock implement reentrant
We make breakpoints where addOne() and get() lock. Then begin the mode:
- When the addOne method obtains the lock, it goes to the “compareAndSetState(0, 1)” of NonfairSync, sets the state value to 1 through CAS, successfully calls, and sets the current thread holding the lock to the current thread T1.
- Set state to 1 from NonfairSync via CAS. The call fails (because it was already held by the current thread T1 lock). Go to else.
- Go to the tryAcquire method of NonfairSync and go further inside;
- The nonfairTryAcquire method in the Sync abstract class is called. I have written the source code explanation below.
Final Boolean nonfairTryAcquire(int acquires) {final Thread current = thread.currentThread (); Int c = getState(); If (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current); if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current); return true; }} // Check whether the current thread is the thread that holds the current lock. Else if (current == getExclusiveOwnerThread()) {// c + acquires = 1 + 1 = 2 int NexTC = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); // Set state to 2 return true; } return false; }Copy the code
At this point, reentrant lock lock process analysis is completed. Debug the Sync abstract class by calling the tryRelease method
Let me summarize here:
- When a thread attempts to acquire a lock, the reentrant lock first attempts to acquire and update the state value
If state == 0 means that no other thread is executing synchronized code, setting state to 1 via CAS succeeds and the current thread continues execution. If the status! If the current thread is the thread that acquired the lock, state+1 is executed and the current thread can acquire the lock again.
- When the lock is released, the reentrant lock also obtains the current state value first, provided that the current thread is the thread holding the lock.
If status-1 == 0, it indicates that all repeated lock acquisition operations of the current thread have been completed before the thread actually releases the lock.
Note the definition of the state variable. In fact, AQS implementation classes control the lock state by controlling the value of state. It is modified by volatile to ensure visibility.
private volatile int state;
Copy the code
Extension: How to implement a non-reentrant lock via AQS state? Make these two points clear:
- When acquiring a lock: To get and try to update the current status value, if status! If = 0, it will fail to acquire the lock and the current thread will block.
- Lock release: After determining that the current thread is the thread holding the lock, set status to 0 to release the lock.
5. Reentrant lock features
One advantage of reentrant locks is that deadlocks can be avoided to some extent. Reentrant locks can avoid waiting for certain threads, so it is conceivable that the performance of reentrant locks is higher than that of non-reentrant locks. You can write a program to test it!!
Java memory model – Volatile application (example explained) Three applications of synchronized (example explained) fully understand the principle of synchronized, lock upgrade article understand Java thread pool