In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >
Share
Shulou(Shulou.com)06/01 Report--
In this issue, the editor will bring you about how to deeply understand the principle of ReentrantLock. The article is rich in content and analyzes and narrates it from a professional point of view. I hope you can get something after reading this article.
What is ReentrantLock?
ReentrantLock is a typical exclusive mode AQS, where a synchronization status of 0 indicates idle. When a thread gets an idle synchronization state, it adds 1 to the synchronization state and changes the synchronization state to non-idle, so other threads suspend waiting. When you modify the synchronization state, and record your own thread as the basis for subsequent reentry, that is, when a thread holds the lock of an object, it can be successful to acquire the lock of the object again. If it is a non-reentrant lock, it will cause a deadlock.
ReentrantLock will involve fair lock and unfair lock, and the key to implementation lies in the different implementation of the member variable sync, which is the core of the lock to achieve mutex synchronization.
/ / variables private final Sync sync; / / parent class abstract static class Sync extends AbstractQueuedSynchronizer {} / / fair lock subclass static final class FairSync extends Sync {} / / unfair lock subclass static final class NonfairSync extends Sync {}
What are fair locks and unfair locks? What's the difference?
What are fair locks and unfair locks? What's the difference?
Fair lock means that when the lock is available, the thread with the longest waiting time on the lock will get the right to use the lock, that is, first-in, first-out. On the other hand, the unfair lock randomly allocates this right of use, which is a kind of preemption mechanism, which obtains the lock randomly, and does not necessarily get the lock first.
ReentrantLock provides a constructor that can implement fair or unfair locks:
Public ReentrantLock (boolean fair) {sync = fair? New FairSync (): new NonfairSync ();}
Although the fair lock is guaranteed in fairness, the fair acquisition lock does not take into account the operating system's scheduling of threads and other factors, which will affect the performance.
Although the unfair mode is more efficient, if there are enough threads applying for the lock, some threads may not get the lock for a long time, which is the "hunger" problem of the unfair lock.
But in most cases we use unfair locks because their performance is much better than fair locks. But fair locks can avoid thread hunger and are useful in some cases.
Let's take a look at the implementation of the ReentrantLock fair lock:
Implementation of ReentrantLock::lock Fair Lock Mode
First of all, you need to input true into the builder function to create a fair lock.
ReentrantLock reentrantLock = new ReentrantLock (true)
Call lock () to lock, and acquire (1) to lock directly.
Public void lock () {/ / calls the lock () method of FairSync, a subclass of sync: ReentrantLock.FairSync.lock () sync.lock ();} final void lock () {/ / calls the acquire () method of AQS to acquire the lock, passing a value of 1 acquire (1);}
Try to acquire the lock directly
/ / AbstractQueuedSynchronizer.acquire () public final void acquire (int arg) {/ / attempt to acquire lock / / if failed, queue if (! tryAcquire (arg) & & / / Note addWaiter () the node mode passed here is the exclusive mode acquireQueued (addWaiter (Node.EXCLUSIVE), arg) selfInterrupt ();}
Specific lock acquisition process
GetState () gets the state value of the synchronization status to determine whether it is 0:
If the value of the state variable is 0, no one has owned the lock yet, and the use of hasQueuedPredecessors () ensures that both new and queued threads use locks sequentially. If no other threads are queuing, then the current thread tries to update the value of state to 1 and set it to the exclusiveOwnerThread variable to prepare for reentering the lock later.
If there is a lock in the exclusiveOwnerThread for the current thread, and now you are trying to acquire the lock, you need to state+1 the value of the state variable
/ / ReentrantLock.FairSync.tryAcquire () protected final boolean tryAcquire (int acquires) {final Thread current = Thread.currentThread (); int c = getState () The value of the state variable is 0, indicating that no thread has occupied the lock if (c = = 0) {/ / hasQueuedPredecessors (), which ensures that both new and queued threads sequentially use the lock if (! hasQueuedPredecessors () & & compareAndSetState (0, acquires)) {/ / the current thread acquires the lock and sets the thread to the lock variable. / / you can reenter the lock to prepare for setExclusiveOwnerThread (current) Return true;}} / / is said to reenter the lock because if it fails to acquire the lock, it will again determine whether the current thread already holds the lock else if (current = = getExclusiveOwnerThread ()) {int nextc = c + acquires; if (nextc).
< 0) throw new Error("Maximum lock count exceeded"); // 设置到state中 // 因为当前线程占有着锁,其它线程只会CAS把state从0更新成1,是不会成功的 // 所以不存在竞争,自然不需要使用CAS来更新 setState(nextc); return true; } return false;} 如果获取失败加入队列里,那具体怎么处理呢?通过自旋的方式,队列中线程不断进行尝试获取锁操作,中间是可以通过中断的方式打断, 如果当前节点的前一个节点为head节点,则说明轮到自己获取锁了,调用tryAcquire()方法再次尝试获取锁 final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; // 自旋 for (;;) { // 当前节点的前一个节点, final Node p = node.predecessor(); // 如果当前节点的前一个节点为head节点,则说明轮到自己获取锁了 // 调用ReentrantLock.FairSync.tryAcquire()方法再次尝试获取锁 if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC // 未失败 failed = false; return interrupted; } // 是否需要阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) // 如果失败了,取消获取锁 cancelAcquire(node); }} 当前的Node的上一个节点不是Head,是需要判断是否需要阻塞,以及寻找安全点挂起。 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 上一个节点的等待状态 int ws = pred.waitStatus; // 等待状态为SIGNAL(等待唤醒),直接返回true if (ws == Node.SIGNAL) return true; // 前一个节点的状态大于0,已取消状态 if (ws >0) {/ / remove all previously canceled nodes from the linked list do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0); pred.next = node;} else {/ / the status of the previous Node is less than or equal to 0, its state is set to wait for wake-up compareAndSetWaitStatus (pred, ws, Node.SIGNAL) } return false;}
After watching the process of acquiring locks, do you know how ReentrantLock implements fair locks? It's actually in the implementation of tryAcquire ().
How does ReentrantLock achieve fair locking?
The use of hasQueuedPredecessors () in the implementation of tryAcquire () ensures that the thread uses locks in the first-in-first-out FIFO and does not cause "hunger" problems.
Protected final boolean tryAcquire (int acquires) {final Thread current = Thread.currentThread (); int c = getState () The value of the state variable is 0, indicating that no thread has occupied lock if (c = = 0) {/ / hasQueuedPredecessors (), which ensures that both new and queued threads will sequentially use lock if (! hasQueuedPredecessors () & & compareAndSetState (0, acquires)) {.... }.}} public final boolean hasQueuedPredecessors () {Node t = tail; Node h = head; Node s; return h! = t & & (s = h.next) = = null | | s.thread! = Thread.currentThread ();}
TryAcquire checks to see if there are still precursors in the CLH queue, and if so continue to wait, in this way to ensure first-come-first-served principle.
So how does ReentrantLock achieve reentrant? How did you re-enter?
How does ReentrantLock achieve reentrant?
In fact, it is also very simple, after acquiring the lock, set an identification variable to the current thread exclusiveOwnerThread, when the thread enters again to judge whether the exclusiveOwnerThread variable is equal to the current thread.
The value of the protected final boolean tryAcquire (int acquires) {/ / state variable is 0, indicating that no thread has occupied the lock if (c = = 0) {if (! hasQueuedPredecessors () & & compareAndSetState (0, acquires)) {/ / the current thread has acquired the lock and set the thread to the exclusiveOwnerThread variable. / / you can reenter the lock to prepare for setExclusiveOwnerThread (current) Return true;}} / / is said to reenter the lock because if it fails to acquire the lock, it will again determine whether the current thread already holds the lock else if (current = = getExclusiveOwnerThread ()) {.}}
When we look at the process of acquiring locks by fair locks, we also know that unfair locks acquire locks, so let's take a look.
What is the difference between ReentrantLock fair lock mode and unfair lock acquisition lock?
In fact, the main difference between unfair lock acquisition and lock acquisition is:
Input false or null in the construction function to create a fair lock for creating an unfair lock NonfairSync,true
Unfair lock when acquiring the lock, check the state status first, and then directly execute aqcuire (1), which can improve efficiency.
Final void lock () {if (compareAndSetState (0,1)) / / sets the current thread to the exclusive thread setExclusiveOwnerThread (Thread.currentThread ()); else / / acquires the lock acquire (1);}
The absence of hasQueuedPredecessors () in tryAcquire () ensures that both new and queued threads use locks sequentially.
Other functions are similar. After understanding the acquisition lock, we have a better understanding of the release of the ReentrantLock::unlock () lock, which is also relatively simple.
ReentrantLock::unlock () releases the lock, how does it wake up the thread in the waiting queue?
Release the lock occupied by the current thread
Protected final boolean tryRelease (int releases) {/ / calculate the state value int c = getState ()-releases; / / if the lock is not occupied by the current thread, throw an exception if (Thread.currentThread ()! = getExclusiveOwnerThread ()) throw new IllegalMonitorStateException (); boolean free = false; if (c = = 0) {/ / the number of reentrants of the lock is 0, indicating that the lock was released successfully free = true / / clear the exclusive thread setExclusiveOwnerThread (null);} / / update the state value setState (c); return free;}
If the release is successful, you need to wake up the thread in the waiting queue. First, check whether the status of the header node is SIGNAL. If so, wake up the thread associated with the next node of the header node. If the release fails, return false means unlocking failed.
Set waitStatus to 0
When the next node of the header node is not empty, the node will be awakened directly. If the node is empty, the end of the queue will start traversing forward to find the last node that is not empty, and then wake up.
Private void unparkSuccessor (Node node) {int ws = node.waitStatus; if (ws)
< 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next;//这里的s是头节点(现在是头节点持有锁)的下一个节点,也就是期望唤醒的节点 if (s == null || s.waitStatus >0) {s = null; for (Node t = tail; t! = null & & t! = node; t = t.prev) if (t.waitStatus
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.