ReentrantLock is reentrant, which means that a thread can relock the ReentrantLock it has already locked. The ReentrantLock object holds nested calls that maintain a calculator to track the lock method. After each call to lock(), Unlock () must be invoked to release the lock, so a piece of code protected by a lock can call another method protected by the same lock.
features
ReentrantLock enables fair locking.
A fair lock means that when a lock is available, the thread that has waited the longest on the lock gains access to it. Non-fair locks randomly assign this right of use. Like synchronized, the default ReentrantLock implementation is unfair because unfair locking performs better than fair locking. Of course, a fair lock prevents hunger, but it can also be useful in some situations. A ReentrantLock is created by passing the true argument to create a fair lock. If false is passed or no argument is passed, an unfair lock is created.
ReentrantLock lock = new ReentrantLock(true);
Copy the code
ReentrantLock responds to interrupts
When synchronized is used to implement a lock, the thread blocking the lock will wait until the lock is acquired, which means that the behavior of waiting indefinitely to acquire the lock cannot be interrupted. ReentrantLock gives us a method to obtain the lock lockInterruptibly() in response to interrupts. This method can be used to solve deadlock problems.
Wait while obtaining the lock duration
TryLock () can be passed as a time parameter to wait for the specified time. If no parameter is passed, the result of the lock application is immediately returned: true indicates that the lock was successfully obtained, false indicates that the lock was failed. We can use this method in conjunction with the retry mechanism to better solve the deadlock problem.
Combine Condition to realize waiting notification mechanism
Synchronization combined with wait and notify methods on Object can realize the wait notification mechanism between threads. ReentrantLock can also do this in conjunction with the Condition interface. And it’s much clearer and easier to use than the former.
public class Account { private final Lock mLock = new ReentrantLock(); private final Condition mCondition = mLock.newCondition(); private String mAccountNo; /** * balance */ private double mBalance; private boolean mFlag; public Account(String accountNo, double balance) { mAccountNo = accountNo; mBalance = balance; } public double getBalance() { return mBalance; } /** * @param drawAmount */ public void draw(double drawAmount) {mlock. lock(); try { if (! mFlag) { mCondition.wait(); } else {system.out.println (thread.currentThread ().getName() + "drawAmount:"); mBalance -= drawAmount; System.out.println(mAccountNo + "account balance:" + mBalance); mFlag = false; mCondition.signalAll(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { mLock.unlock(); Public void deposit(double depositAmount) {public void deposit(double depositAmount) {mlock. lock(); try { if (mFlag) { mCondition.wait(); } else {system.out.println (thread.currentThread ().getName() + "depositAmount"); mBalance += depositAmount; System.out.println(mAccountNo + "account balance:" + mBalance); mFlag = true; mCondition.signalAll(); } } catch (InterruptedException e) { e.printStackTrace(); } finally { mLock.unlock(); }}}Copy the code
ReentrantLock is mainly implemented by CAS+AQS queues.
CAS
Compare and Swap. CAS has three operands: the memory value V, the expected value A, and the new value to be modified B. Change the memory value V to B if and only if the expected value A and memory value V are the same, otherwise nothing is done. In either case, it returns the value of that location before the CAS instruction. CAS effectively says “I think position V should contain the value A; If this value is included, place B in this position; Otherwise, do not change the location, just tell me the current value of the location.” This operation is an atomic operation that is widely used in Java’s underlying implementation. Java concurrency (java.util.concurrent) makes heavy use of CAS operations. In Java, CAS is primarily implemented by the sun.misc.Unsafe class through JNI calls to CPU low-level instructions.
AQS
AbstractQueuedSynchronizer, is used to build a container frame locks and synchronization. In fact, many classes in the Concurrent package are built based on AQS, such as ReentrantLock, Semaphore, CountDownLatch, ReentrantReadWriteLock, FutureTask, etc. AQS addresses many of the details of design when implementing synchronous containers.
AQS uses a FIFO queue to represent the thread waiting for the lock. The queue head node is called “sentinel node” or “dumb node”, which is not associated with any thread. The other nodes are associated with wait threads, and each node maintains a waitStatus, waitStatus.