In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article mainly explains the "Java concurrency of ReentrantLock and AQS source code analysis", the article explains the content is simple and clear, easy to learn and understand, the following please follow the editor's ideas slowly in-depth, together to study and learn "Java concurrency of ReentrantLock and AQS source code analysis" it!
one。 Preface
First of all, before talking about ReentrantLock, we need to know the cornerstone of the concurrent synchronization of the entire JUC. All the shared variables in currrent are modified by volatile. We know that the semantics of volatile have two major features, visibility and prevention of reordering (memory barrier, volatie write and volatile read).
1. When the second operation is a volatile write operation, no matter what the first operation is (normal read-write or volatile read-write), it cannot be reordered. This rule ensures that all operations before volatile are written will not be reordered after volatile
2. When the first operation is a volatile read operation, no matter what the second operation is, it cannot be reordered. This rule ensures that all operations after volatile read are not reordered before volatile
3. When the first operation is a volatile write operation, the second operation is a volatile read operation, which cannot be reordered.
The cas operation also contains volatile write / read semantics, and the perfect combination of the two constitutes the cornerstone of current.
II. Basic usage of ReentrantLock
1.
Public class ReentrantLockText {public static void main (String [] args) {Lock lock = new ReentrantLock (); Thread T1 = new Thread (()-> {try {lock.lock ()) System.out.println ("T1 start"); TimeUnit.SECONDS.sleep (Integer.MAX_VALUE); System.out.println ("T1 end") } catch (InterruptedException e) {System.out.println ("interrupted!");} finally {lock.unlock ();}}); t1.start () Thread T2 = new Thread (()-> {try {/ / lock.lock (); lock.lockInterruptibly (); / / you can respond to the interrupt () method System.out.println ("T2 start") TimeUnit.SECONDS.sleep (5); System.out.println ("T2 end");} catch (InterruptedException e) {System.out.println ("interrupted!") } finally {lock.unlock ();}}); t2.start (); try {TimeUnit.SECONDS.sleep (1) } catch (InterruptedException e) {e.printStackTrace ();} t2.interrupt (); / / interrupt the waiting of thread 2}}
Running result
Reentrantlock is used to replace synchronized
It is important to note that the lock must be released manually (say the important thing three times)
When using syn locking, jvm will automatically release the lock if it encounters an exception, but lock must release the lock manually, so the lock is often released in finally.
Using reentrantlock, you can "try to lock" the tryLock, so it cannot be locked, or cannot be locked for a specified period of time, and the thread can decide whether to continue to wait.
Using ReentrantLock, you can also call the lockInterruptibly method, which can respond to the thread interrupt method
Can be interrupted while a thread is waiting for a lock
2.ReentrantLock also has a tryLock (time), which can specify the time. If the lock is not acquired within the specified time, it will be abandoned. You can decide whether to continue waiting by its return value.
3. And then there is Condition (I personally think this is the most flexible place)
Public class Lock_condition {public static void main (String [] args) {char [] aI = "1234567" .toCharArray (); char [] aC = "ABCDEFG" .toCharArray (); Lock lock = new ReentrantLock (); Condition conditionT1 = lock.newCondition (); Condition conditionT2 = lock.newCondition (); new Thread (()-> {try {charray ()) For (char c: aI) {System.out.print (c); conditionT2.signal (); conditionT1.await ();} conditionT2.signal ();} catch (Exception e) {e.printStackTrace () } finally {lock.unlock ();}}, "T1"). Start (); new Thread ()-> {try {lock.lock (); for (char c: aC) {System.out.print (c); conditionT1.signal () ConditionT2.await ();} conditionT1.signal ();} catch (Exception e) {e.printStackTrace ();} finally {lock.unlock ();}, "T2"). Start ();}}
This is the use of condition combined with lock.
Condition only implements exclusive locks at present. The understanding of the source code of condition will be updated later. For the time being, we only need to know wait and notifiy like object.
three。 Principle + source code
Now that we know the basic usage, we can start to explore the source code
1.AQS
We know that the core class in JUC is AQS, so what on earth is AQS?
1) add the internal class NODE source code first
Static final class Node {static final Node SHARED = new Node (); static final Node EXCLUSIVE = null; static final int CANCELLED = 1; static final int SIGNAL =-1; static final int PROPAGATE =-3; volatile int waitStatus; volatile Node next; volatile Node prev; volatile Thread thread; Node nextWaiter
I don't know how everyone felt when they saw this Node next;Node prev;. Anyway, I was very excited at that time. Isn't this just a two-way linked list?
Look at the attribute volatile Thread thread;, which is a two-way linked list that manages threads. In other words, threads are packaged into nodes and placed in the linked list of AQS.
After the structure of the foundation is clear.
Do SHARED and EXCLUSIVE represent exclusive nodes or shared nodes
2) add the AQS attribute source code
Private transient volatile Node head; private transient volatile Node tail; private volatile int state
Needless to say, Node0 head and tail are here to manage nodes.
Here we want to introduce the core property is state, which is also the soul of the class AQS.
1. In the exclusive lock, the state is 1 or 0 (if it is greater than 1, the lock is reentered, which will be analyzed later)
2 represents how many shared lock resources are left in the shared lock
3. In read-write locks, the higher 16 bits represent whether the write locks are occupied, and the lower 16 bits represent how many read locks there are.
4. In CountDownLatch, by constructing the parameter code, the remaining number of gates
5. In Semaphore, the number of semaphores is also represented by construction parameters.
2.ReentrantLock acquires lock source code (exclusive lock)
First of all, fair lock and unfair lock inherit from Sync respectively.
FairSync NoFairSync, which is an unfair lock by default, can be specified on the constructor
ReentrantLock lock = new ReentrantLock (true); / / ture is a fair lock
Fair lock is the name that manages a thread queue in AQS. If a thread comes to grab the lock at this time, if it is a fair lock, it will judge whether there is a waiting queue (FIFO) that is different from the current thread. If it exists, it will queue, and the unfair lock will queue directly.
(2.1. Unfair lock acquisition lock)
Final void lock () {if (compareAndSetState (0,1)) / / cas atomic operation attempts to modify the value. If the modification is successful, the lock setExclusiveOwnerThread (Thread.currentThread ()); else acquire (1);}
First of all, the cas atomic operation attempts to modify the value. If the modification is successful, it means that the lock has been acquired successfully. Enter the setExclusiveOwnerThread () method.
Protected final void setExclusiveOwnerThread (Thread thread) {exclusiveOwnerThread = thread;}
Record the current thread and realize the bias lock, and one line of code will perfectly realize the bias lock!
If it fails, call acquire (); this method actually calls the nonfairTryAcquire method of the subclass
Final boolean nonfairTryAcquire (int acquires) {final Thread current = Thread.currentThread (); int c = getState (); if (c = = 0) {if (compareAndSetState (0, acquires)) {setExclusiveOwnerThread (current); return true }} else if (current = = getExclusiveOwnerThread ()) {int nextc = c + acquires; if (nextc
< 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } 首先,获取state的值,判断是否为0,如果为0,则说明锁没有被占有,(可能是刚刚被释放)那么cas操作开始尝试获取锁, **(注意注意注意)**重要的事情说三遍,这里仅仅尝试获取一次,没有自旋!!这是独占锁与共享锁的区别之一,因为如果state>= 0 (for shared locks, state represents the remaining number), then the shared lock will keep trying to spin to acquire the lock, state 0) {/ * * Predecessor was cancelled. Skip over predecessors and * indicate retry. * / do {node.prev = pred = pred.prev;} while (pred.waitStatus > 0); pred.next = node;} else {/ * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. * / compareAndSetWaitStatus (pred, ws, Node.SIGNAL);} return false;}
Static final int CANCELLED = 1
Static final int SIGNAL =-1
Static final int CONDITION =-2
Static final int PROPAGATE =-3
CANCELLED: because of timeout or interruption, the node will be set to cancel state. The node in the cancelled state should not compete for locks, but can only keep the canceled state unchanged and cannot switch to other states. Nodes in this state will be kicked out of the queue and reclaimed by GC
SIGNAL: indicates that the successor node of this node is blocked. It needs to be notified then.
CONDITION: indicates that the node is in a conditional queue and is blocked because it is waiting for a condition
PROPAGATE: using the header node in shared mode, it is possible that the card is in this state, indicating that the next acquisition of the lock can be propagated unconditionally.
0:None of the above, the new node will be in this state.
First of all, let's explain the attribute waitStatus. Why didn't we mention it before? because we didn't operate on waitStatus before, we didn't consider this attribute when we did the new node and the encapsulation node, so now we treat it as a new attribute with a value of 0.
What is the logical meaning of the shouldParkAfterFailedAcquire code?
If it's SIGNAL, then ture directly.
If it is greater than 0, it is CANCELLED, which is cancelled, and the queue is eliminated directly.
If neither, then set the precursor node to SIGNAL, that is, you can sleep peacefully, set the alarm clock, and be awakened.
We are obviously the third kind now, because we haven't done anything before, which is zero.
So the original state
After shouldParkAfterFailedAcquire
So now in the linked list, set the alarm clock for the precursor of thread 1, 0 becomes-1
Suppose there is another thread 2 at this time, then similarly, set the alarm clock 0 to-1 for the pioneer of the thread.
After calling shouldParkAfterFailedAcquire (), call the parkAndCheckInterrupt method to block
To mention here, the blocking used in parkAndCheckInterrupt,lock is based on lockSupper.park () and lockSupper.unpark (), and lockSupper calls the unsafe class. We know that java is based on jvm and cannot operate on os directly like C++, so jvm provides us with a ladder, which is the unsafe class, which directly blocks the intersection of the thread to the operating system.
Four, release the lock.
Finally, when the lock is released, the logic of releasing the lock of the exclusive lock is relatively simple with that of the shared lock, and I will continue to update the source code of the shared lock later.
Public final boolean release (int arg) {if (tryRelease (arg)) {Node h = head; if (h! = null & & h.waitStatus! = 0) unparkSuccessor (h); return true;} return false;}
First, let's look at the tryRelease method.
Protected final boolean tryRelease (int releases) {int c = getState ()-releases; if (Thread.currentThread ()! = getExclusiveOwnerThread ()) throw new IllegalMonitorStateException (); boolean free = false; if (c = = 0) {free = true; setExclusiveOwnerThread (null);} setState (c) Return free;}
First of all, int c = getState ()-releases; where c may be > 0, because the reentrant lock of the exclusive lock (above and the source code operation that describes the reentrant of the exclusive lock), it may need to be unlocked multiple times, continue
Determine whether the current thread is an exclusive thread, if not, report an IllegalMonitorStateException exception
If the thread is unlocked until clocked 0, setExclusiveOwnerThread=null is set.
Set the current exclusive thread to null, and then set state to 0
Go back to release. If the header node is not null and h.waitStatus! = 0, it means-1, which means that the alarm clock has been set and the blocking node in the aqs queue needs to be awakened. The unparkSuccessor method is called to continue looking at the source code.
Private void unparkSuccessor (Node node) {/ * * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. * / int ws = node.waitStatus; if (ws
< 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; if (s == null || s.waitStatus >0) {s = null; for (Node t = tail; t! = null & & t! = node; t = t.prev) if (t.waitStatus 0, that is, 1jie CANCELLED, which means it has been cancelled). At this time, start traversing from the tail, eliminate the node with waitStatus > 0, and find the first 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.