Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How is ReentrantLock implemented based on AQS

2025-01-18 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly introduces how ReentrantLock is based on AQS related knowledge, the content is detailed and easy to understand, the operation is simple and fast, with a certain reference value, I believe you will have something to gain after reading this article on how ReentrantLock is based on AQS. Let's take a look.

ReentrantLock is a reentrant mutex based on AQS implementation that has some of the same basic behavior and semantics as using synchronized methods and statements, but is more powerful.

Lock and unlock

Synchronization in ReentrantLock starts with the lock method. Lock acquires the lock, performs a series of business operations, and releases the lock using unlock at the end.

Private final ReentrantLock lock = new ReentrantLock ()

Public void sync () {

Lock.lock ()

Try {

/ / … Method body

} finally {

Lock.unlock ()

}

}

Lock

The implementation of lock in ReentrantLock is realized by calling the AbstractQueuedSynchronizer#acquire method of AQS.

Public final void acquire (int arg) {

/ / attempt to acquire the lock

If (! TryAcquire (arg) & &

AcquireQueued (addWaiter (Node.EXCLUSIVE), arg))

SelfInterrupt ()

}

According to the template method pattern described earlier, the tryAcquire for lock acquisition is implemented in ReentrantLock. The actual implementation method in the unfair lock is nonfairTryAcquire.

ReentrantLock#nonfairTryAcquire

Protected final boolean tryAcquire (int acquires) {

Return nonfairTryAcquire (acquires)

}

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;   }   在获取锁的逻辑中首先是尝试以cas方式获取锁,如果获取失败则表示锁已经被线程持有。   再判断持有该锁的线程是否为当前线程,如果是当前线程就将state的值加1,在释放锁是也需要释放多次。这就是可重入锁的实现。   如果持有锁的线程并非当前线程则这次加锁失败,返回false。加锁失败后将调用 AbstractQueuedSynchronizer#acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 。   首先会调用addWaiter方法将该线程入队。   private Node addWaiter(Node mode) {   Node node = new Node(Thread.currentThread(), mode);   Node pred = tail;   if (pred != null) {   node.prev = pred;   if (compareAndSetTail(pred, node)) {   pred.next = node;   return node;   }   }   enq(node);   return node;   }   mode是指以何种模式的节点入队,这里传入的是Node.EXCLUSIVE(独占锁)。首先将当前线程包装为node节点。然后判断等待队列的尾节点是否为空,如果不为空则通过cas的方式将当前节点接在队尾。如果tail为空则执行enq方法。   AbstractQueuedSynchronizer#enq   private Node enq(final Node node) {   for (;;) {   Node t = tail;   if (t == null) { // Must initialize   if (compareAndSetHead(new Node()))   tail = head;   } else {   node.prev = t;   if (compareAndSetTail(t, node)) {   t.next = node;   return t;   }   }   }   }   enq方法通过for(;;)无限循环的方式将node节点设置到等待队列的队尾(队列为空时head和tail都指向当前节点)。   综上可知addWaiter方法的作用是将竞争锁失败的节点放到等待队列的队尾。   等待队列中的节点也并不是什么都不做,这些节点也会不断的尝试获取锁,逻辑在acquireQueued中实现。   AbstractQueuedSynchronizer#acquireQueued   final boolean acquireQueued(final Node node, int arg) {   boolean failed = true;   try {   boolean interrupted = false;   for (;;) {   final Node p = node.predecessor();   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);   }   }   可以看到该方法也是使用for(;;)无限循环的方式来尝试获取锁。首先判断当前节点是否为头结点的下一个节点,如果是则再次调用tryAcquire尝试获取锁。当然这个过程并不是一定不停进行的,这样的话多线程竞争下cpu切换也极耗费资源。   shouldParkAfterFailedAcquire会判断是否对当前节点进行阻塞,阻塞之后只有当unpark后节点才会继续假如争夺锁的行列。   private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {   int ws = pred.waitStatus;   if (ws == Node.SIGNAL)   return true;   if (ws >

0) {

Do {

Node.prev = pred = pred.prev

} while (pred.waitStatus > 0)

Pred.next = node

} else {

CompareAndSetWaitStatus (pred, ws, Node.SIGNAL)

}

Return false

}

Whether a node needs to be blocked is judged by the state of the previous node of the node.

If the status of the predecessor node is singal, it means that the predecessor node is still waiting and the current node needs to continue to be blocked. Return to true.

If the previous node is greater than 0, it means that the previous node is canceled. The node that cancels the state does not participate in the lock competition and skips directly. Return to false.

If the previous node is in another state (0quotient procate), no blocking occurs, indicating that the current node needs to retry to acquire the lock. Return to false.

If the shouldParkAfterFailedAcquire method returns true, it means that the current node needs to be blocked. The blocking method is parkAndCheckInterrupt.

AbstractQueuedSynchronizer#parkAndCheckInterrupt

Private final boolean parkAndCheckInterrupt () {

LockSupport.park (this)

Return Thread.interrupted ()

}

Blocking is done through LockSupport. The blocked node does not participate in lock competition (no loop is used to acquire lock) and can only continue to compete for lock after unpark.

The release of blocked nodes depends on the unlock method.

Unlock

The implementation of unlock in ReentrantLock is realized by calling the AbstractQueuedSynchronizer#release method of AQS.

Public final boolean release (int arg) {

If (tryRelease (arg)) {

Node h = head

If (h! = null & & h.waitStatus! = 0)

UnparkSuccessor (h)

Return true

}

Return false

}

Release calls the tryRelease method, and tryRelease is implemented in ReentrantLock.

ReentrantLock#tryRelease

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

}

The logic of the tryRelease method is simple: first subtract releases (usually 1) to release a lock. If the released state=0 indicates that the lock is released successfully, the subsequent waiting node can acquire the lock. If statekeeper 0 indicates that the lock is a reentrant lock, it needs to be released multiple times.

When the lock is released successfully (state=0), the successor node of the header node is unpark.

AbstractQueuedSynchronizer#unparkSuccessor

Private void unparkSuccessor (Node node) {

Int ws = node.waitStatus

If (ws

< 0)   compareAndSetWaitStatus(node, ws, 0);   Node s = node.next;   if (s == null || s.waitStatus >

0) {

S = null

For (Node t = tail; t! = null & & t! = node; t = t.prev)

If (node of t.waitStatus 0).

Fair lock and unfair lock

The constructor of ReentrantLock accepts an optional fair parameter. When set to true, locks tend to be assigned to the thread with the longest waiting time when multiple threads compete.

Public ReentrantLock (boolean fair) {

Sync = fair? New FairSync (): new NonfairSync ()

}

In an environment where multiple locks compete for unified resources, AQS maintains a waiting queue to which threads that fail to acquire the lock are hung. If a fair lock is used, the resource is obtained from the header node of the queue.

The only difference between the implementation of the fair lock and the unfair lock according to the code is that the fair lock has one more detection method.

Fair lock

Protected final boolean tryAcquire (int acquires) {

/ / …

If (c = = 0) {

If (! HasQueuedPredecessors () / /! hasQueuedPredecessors () is a more operation than an unfair lock.

& & compareAndSetState (0, acquires)) {

SetExclusiveOwnerThread (current)

Return true

}

}

/ / …

Return false

}

HasQueuedPredecessors ()

Public final boolean hasQueuedPredecessors () {

Node t = tail; / / Read fields in reverse initialization order

Node h = head

Node s

Return h! = t & & (s = h.next) = = null | | s.thread! = Thread.currentThread ())

}

The logic of the method is simple, that is, if there are nodes in the waiting queue and the node that is not at the top of the list is not the node in which the current thread is located, it returns true to indicate that there are nodes waiting longer.

This is the end of the article on "how ReentrantLock is implemented based on AQS". Thank you for reading! I believe that everyone has a certain understanding of the knowledge of "how ReentrantLock is implemented based on AQS". If you want to learn more knowledge, welcome to follow the industry information channel.

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.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report