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 to use fair lock and unfair lock in Java AQS

2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

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

This article mainly explains "how to use fair lock and unfair lock in Java AQS". Interested friends may wish to take a look. The method introduced in this paper is simple, fast and practical. Now let the editor take you to learn how to use fair locks and unfair locks in Java AQS.

Fair lock and unfair lock

ReentrantLock defaults to unfair locks unless you pass the parameter true in the constructor.

Public ReentrantLock () {/ / default unfair lock sync = new NonfairSync ();} public ReentrantLock (boolean fair) {sync = fair? New FairSync (): new NonfairSync ();}

Lock method of fair lock:

Static final class FairSync extends Sync {final void lock () {acquire (1);} / AbstractQueuedSynchronizer.acquire (int arg) public final void acquire (int arg) {if (! tryAcquire (arg) & & acquireQueued (addWaiter (Node.EXCLUSIVE), arg)) selfInterrupt ();} protected final boolean tryAcquire (int acquires) {final Thread current = Thread.currentThread () Int c = getState (); if (c = = 0) {/ / 1\. Compared with the unfair lock, there is one more judgment: whether there is a thread waiting for 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; }} 非公平锁的 lock 方法: static final class NonfairSync extends Sync { final void lock() { // 2\. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了 if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // AbstractQueuedSynchronizer.acquire(int arg) public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }}/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */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;} 总结:公平锁和非公平锁只有两处不同: 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。 公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。 相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。 Condition Tips: 这里重申一下,要看懂这个,必须要先看懂上一篇关于 AbstractQueuedSynchronizer 的介绍,或者你已经有相关的知识了,否则这节肯定是看不懂的。 我们先来看看 Condition 的使用场景,Condition 经常可以用在生产者-消费者的场景中,请看 Doug Lea 给出的这个例子: import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class BoundedBuffer { final Lock lock = new ReentrantLock(); // condition 依赖于 lock 来产生 final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; // 生产 public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); // 队列已满,等待,直到 not full 才能继续生产 items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); // 生产成功,队列已经 not empty 了,发个通知出去 } finally { lock.unlock(); } } // 消费 public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); // 队列为空,等待,直到队列 not empty,才能继续消费 Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); // 被我消费掉一个,队列 not full 了,发个通知出去 return x; } finally { lock.unlock(); } }} 1、我们可以看到,在使用 condition 时,必须先持有相应的锁。这个和 Object 类中的方法有相似的语义,需要先持有某个对象的监视器锁才可以执行 wait(), notify() 或 notifyAll() 方法。 2、ArrayBlockingQueue 采用这种方式实现了生产者-消费者,所以请只把这个例子当做学习例子,实际生产中可以直接使用 ArrayBlockingQueue 我们常用 obj.wait(),obj.notify() 或 obj.notifyAll() 来实现相似的功能,但是,它们是基于对象的监视器锁的。需要深入了解这几个方法的读者,可以参考我的另一篇文章《 深入分析 java 8 编程语言规范:Threads and Locks》。而这里说的 Condition 是基于 ReentrantLock 实现的,而 ReentrantLock 是依赖于 AbstractQueuedSynchronizer 实现的。 在往下看之前,读者心里要有一个整体的概念。condition 是依赖于 ReentrantLock 的,不管是调用 await 进入等待还是 signal 唤醒,都必须获取到锁才能进行操作。 每个 ReentrantLock 实例可以通过调用多次 newCondition 产生多个 ConditionObject 的实例: final ConditionObject newCondition() { // 实例化一个 ConditionObject return new ConditionObject();} 我们首先来看下我们关注的 Condition 的实现类 AbstractQueuedSynchronizer 类中的 ConditionObject。 public class ConditionObject implements Condition, java.io.Serializable { private static final long serialVersionUID = 1173984872572414699L; // 条件队列的第一个节点 // 不要管这里的关键字 transient,是不参与序列化的意思 private transient Node firstWaiter; // 条件队列的最后一个节点 private transient Node lastWaiter; ...... 在上一篇介绍 AQS 的时候,我们有一个阻塞队列,用于保存等待获取锁的线程的队列。这里我们引入另一个概念,叫条件队列(condition queue),我画了一张简单的图用来说明这个。 这里的阻塞队列如果叫做同步队列(sync queue)其实比较贴切,不过为了和前篇呼应,我就继续使用阻塞队列了。记住这里的两个概念,阻塞队列和条件队列。

Here, let's briefly review the properties of Node:

Available values of volatile int waitStatus; / / are 0, CANCELLED (1), SIGNAL (- 1), CONDITION (- 2), PROPAGATE (- 3) volatile Node prev;volatile Node next;volatile Thread thread;Node nextWaiter

Prev and next are used to implement the bi-directional linked list of blocking queues, and the nextWaiter here is used to implement the unidirectional linked list of conditional queues.

Basically, if you understand this picture, you will know the processing flow of condition. So, I'll briefly explain the diagram first, and then explain the code implementation in detail.

The nodes of the conditional queue and the blocking queue are both instances of Node because the nodes of the conditional queue need to be transferred to the blocking queue.

We know that a ReentrantLock instance can generate multiple Condition instances by calling newCondition () multiple times, which corresponds to condition1 and condition2. Note that ConditionObject has only two properties, firstWaiter and lastWaiter

Each condition has an associated conditional queue. For example, thread 1 can wrap the current thread 1 as a Node and add it to the conditional queue by calling the condition1.await () method, and then block here and do not continue to execute. The conditional queue is an one-way linked list.

Call condition1.signal () to trigger a wake-up call, which wakes up the queue head. The firstWaiter of the conditional queue corresponding to condition1 will be moved to the end of the blocking queue, waiting for the lock to be acquired, and then the await method can return and continue to execute.

The above 2-> 3-> 4 describes the simplest process without taking into account interrupts, signalAll, and await methods with timeout parameters, but understanding here is the main purpose of this section.

At the same time, you can also see intuitively from the figure which operations are thread-safe and which are thread-unsafe.

After reading this diagram, the following code analysis is simple.

Next, let's take a step-by-step code analysis step by step. Let's first look at the wait method:

/ / first of all, this method is interruptible, what is uninterruptible is that another method awaitUninterruptibly () / / this method blocks until the signal method (refers to signal () and signalAll (), the same below) is called, or the interrupted public final void await () throws InterruptedException {/ / old rule, since the method is to respond to interrupts, determine the interrupt state if (Thread.interrupted ()) throw new InterruptedException () at the beginning / / add Node node = addConditionWaiter () to the conditional queue of condition; / / release the lock, and the return value is the state value before releasing the lock / / await (). The current thread must hold the lock, so be sure to release int savedState = fullyRelease (node); int interruptMode = 0; / / there are two cases of exiting the loop, and then analyze / / 1\ carefully. IsOnSyncQueue (node) returns true, that is, the current node has been transferred to the blocking queue / / 2\. CheckInterruptWhileWaiting (node)! = 0 goes to break and then exits the loop, which means that the thread interrupts while (! isOnSyncQueue (node)) {LockSupport.park (this); if ((interruptMode = checkInterruptWhileWaiting (node))! = 0) break;} / / wakes up, enters the blocking queue and waits for the lock if (acquireQueued (node, savedState) & & interruptMode! = THROW_IE) interruptMode = REINTERRUPT If (node.nextWaiter! = null) / / clean up if cancelled unlinkCancelledWaiters (); if (interruptMode! = 0) reportInterruptAfterWait (interruptMode);}

In fact, I also generally said that the whole await process nine times out of ten, let's step by step to explain the above points with the source code.

1. Add a node to a conditional queue

AddConditionWaiter () is to add the current node to the conditional queue. Looking at the figure, we know that the operation in this conditional queue is thread safe.

/ / queue the node corresponding to the current thread and insert it at the end of the queue private Node addConditionWaiter () {Node t = lastWaiter; / / if the last node of the conditional queue is cancelled, clear it out / / Why is it determined that the cancellation queue has occurred if waitStatus is not equal to Node.CONDITION here? If (t! = null & & t.waitStatus! = Node.CONDITION) {/ / this method traverses the entire conditional queue and then clears all cancelled nodes from the queue unlinkCancelledWaiters (); t = lastWaiter;} / / node specifies waitStatus as Node.CONDITION Node node = new Node (Thread.currentThread (), Node.CONDITION) when initializing / / t is lastWaiter at the end of the queue / / if the queue is listed as empty if (t = = null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node;}

The above code is simple enough to put the current thread at the end of the conditional queue.

In the addWaiter method, there is a unlinkCancelledWaiters () method that clears the queue of nodes that have canceled waiting.

If a cancel occurs during await (which will be said later), or when the node joins the queue, it is found that the last node has been cancelled, this method will be called once.

/ / waiting queue is an one-way linked list. Traversing the list will clear out the nodes that have cancelled waiting. / / it is only a linked list operation. It is easy to understand. You can private void unlinkCancelledWaiters () {Node t = firstWaiter; Node trail = null; while (t! = null) {Node next = t.nextWaiter. / / if the status of the node is not Node.CONDITION, the node is the cancelled if (t.waitStatus! = Node.CONDITION) {t.nextWaiter = null; if (trail = = null) firstWaiter = next; else trail.nextWaiter = next; if (next = = null) lastWaiter = trail } else trail = t; t = next;}} 2. Completely release the exclusive lock

Back in the wait method, after the node joins the queue, it calls int savedState = fullyRelease (node); the method releases the lock, notice that the exclusive lock (fully release) is completely released here, because ReentrantLock is reentrant.

Consider the savedState here. If the thread performs two lock () operations before condition1.await (), then state is 2, we understand that the thread holds two locks, where the await () method must set state to 0 and then enter the suspended state so that other threads can hold the lock. When it is awakened, it needs to re-hold 2 locks in order to continue.

/ / first, we need to observe that the return value savedState represents the state value before release / / for the simplest operation: first lock.lock (), then condition1.await (). / / then state changes from 1 to 0 after this method, and the lock is released, and this method returns 1 savedState / corresponding, if lock is reentered n times, savedState = = n int savedState / if this method fails, the node will be set to "cancel" state and an exception IllegalMonitorStateExceptionfinal int fullyRelease (Node node) {boolean failed = true; try {int savedState = getState () will be thrown. / / the current state is used as the parameter of release, that is, the lock is completely released, and the state is set to 0 if (release (savedState)) {failed = false; return savedState;} else {throw new IllegalMonitorStateException ();}} finally {if (failed) node.waitStatus = Node.CANCELLED;}}

Consider that if a thread calls the condition1.await () method without holding lock, it can enter the conditional queue, but in the above method, since it does not hold a lock, the release (savedState) method must return false, enter the exception branch, and then enter the finally block to set node.waitStatus = Node.CANCELLED. The node that has been queued will be "please leave" by the subsequent node.

3. Waiting to enter the blocking queue

After releasing the lock, this is the next segment, where you spin, and if you find yourself not in the blocking queue, hang up and wait to be transferred to the blocking queue.

Int interruptMode = 0 if / if it is not in the blocking queue, note that it is the blocking queue while (! isOnSyncQueue (node)) {/ / thread suspending LockSupport.park (this); / / you don't have to look at it until you see when it is unpark ((interruptMode = checkInterruptWhileWaiting (node))! = 0) break;}

IsOnSyncQueue (Node node) is used to determine whether the node has been transferred to the blocking queue:

/ / when the node enters the conditional queue, waitStatus = Node.CONDITION// is set during initialization. As I mentioned earlier, when signal, the node needs to be moved from the conditional queue to the blocking queue. / / this method is to determine whether the node has been moved to the blocking queue. When final boolean isOnSyncQueue (Node node) {/ / moves past, the waitStatus of node will be set to 0. After this, when talking about the signal method, we will say / / if waitStatus is still Node.CONDITION, that is-2, then it must still be in the conditional queue / / if the precursor prev of node points to or null, it means that there must be no blocking queue (prev is used in the blocking queue list) if (node.waitStatus = Node.CONDITION | | node.prev = = null) return false / / if node already has a successor node next, it must be blocking queue if (node.next! = null) return true / / the following method starts from the back-to-front traversal of the end of the blocking queue. If it is equal, it means it is in the blocking queue, otherwise it is not in the blocking queue / / can it be inferred that node is in the blocking queue by judging node.prev ()! = null? The answer is: no. / / you can see the enrollment method of the previous AQS. First, set node.prev to point to tail, / / then the CAS operation sets itself to the new tail, but this time CAS may fail. Return findNodeFromTail (node);} / / traverses from the end of the blocking queue and returns trueprivate boolean findNodeFromTail (Node node) {Node t = tail; for (;;) {if (t = = node) return true; if (t = = null) return false; t = t.queue;}} if found

Going back to the previous loop, if isOnSyncQueue (node) returns false, then go to LockSupport.park (this); here the thread hangs.

4. Signal wakes up the thread and moves to the blocking queue

For your understanding, let's look at the wake-up operation here because we just got to LockSupport.park (this); hang the thread and wait for wake-up.

The wake-up operation is usually performed by another thread, just as in the producer-consumer model, if the thread hangs because it is waiting for consumption, then when the producer produces something, it will call signal to wake up the waiting thread to consume.

/ / Wake up the longest waiting thread / / that is, the node corresponding to this thread is transferred from the conditional queue to the blocking queue public final void signal () {/ / the thread calling the signal method must hold the current exclusive lock if (! isHeldExclusively ()) throw new IllegalMonitorStateException (); Node first = firstWaiter; if (first! = null) doSignal (first) } / / iterate back from the head of the conditional queue to find the first node// that needs to be transferred, because as we said earlier, some threads will cancel the queue, but may still be in the queue private void doSignal (Node first) {do {/ / point the firstWaiter to the first one behind the first node, because the first node is about to leave / / if the first is removed, there are no nodes waiting behind Then you need to set lastWaiter to null if ((firstWaiter = first.nextWaiter) = = null) lastWaiter = null / / because first is about to be moved to the blocking queue, and the link with the conditional queue is broken here first.nextWaiter = null;} while (! transferForSignal (first) & & (first = firstWaiter)! = null) / / here in the while loop, if the first transfer is not successful, select the first node after the first for transfer, and so on} / / transfer the node from the conditional queue to the blocking queue / / true represents the successful transfer / / false represents that before the signal, the node has cancelled the final boolean transferForSignal (Node node) {/ / CAS. If it fails, the waitStatus of this node is no longer Node.CONDITION, indicating that the node has been cancelled. / / now that it has been cancelled, there is no need to transfer. The method returns and transfers the next node / / otherwise, set waitStatus to 0 if (! compareAndSetWaitStatus (node, Node.CONDITION, 0)) return false / / enq (node): spin into the end of the blocking queue / / Note: the return value here is Node p = enq (node), the leading node of node in the blocking queue; int ws = p.waitStatus; / / ws > 0 means that the leading node of node in the blocking queue cancels the waiting lock and directly wakes up the thread corresponding to node. What happens after waking up? explain later / / if ws 0 | |! compareAndSetWaitStatus (p, ws, Node.SIGNAL)) / / if the precursor node is canceled or CAS fails, it will come here to wake up the thread, and then see the next section LockSupport.unpark (node.thread); return true;}

Normally, ws > 0 | |! compareAndSetWaitStatus (p, ws, Node.SIGNAL), ws transfers the node to the blocking queue-> acquires the lock (unpark)

The thread is interrupted. During park, another thread interrupted the thread

As we said in signal, the precursor node after the transfer was cancelled, or the CAS operation to the precursor node failed.

False awakening. This also exists, and similar to Object.wait (), both have this problem.

The first step after the thread wakes up is to call the checkInterruptWhileWaiting (node) method, which is used to determine whether an interrupt occurred during the thread suspension, and if so, whether it was interrupted before the signal call or after the signal.

/ / 1\. If it has been interrupted before signal, return THROW_IE// 2. If it is interrupted after signal, return REINTERRUPT// 3. No interruption occurred, return 0private int checkInterruptWhileWaiting (Node node) {return Thread.interrupted ()? (transferAfterCancelledWait (node)? THROW_IE: REINTERRUPT): 0;}

Thread.interrupted (): if the current thread is already in an interrupted state, this method returns true and resets the interrupted state to false, so there is a subsequent use of REINTERRUPT.

See how to determine whether an outage occurred before or after the signal:

/ / this method is called only if the thread is interrupted / / if necessary, transfer the node that has cancelled waiting to the blocking queue / / return true: if the thread is cancelled before signal, final boolean transferAfterCancelledWait (Node node) {/ / sets the node state to 0 / / with CAS. If this step CAS succeeds, it is the interrupt that occurred before the signal method. Because if signal happens first, waitStatus will be set to 0 if in signal (compareAndSetWaitStatus (node, Node.CONDITION, 0)) {/ / put the node into the blocking queue / / here we see that even if interrupted, it will still be transferred to the blocking queue enq (node) Return true } / / this is because CAS failed, it must be because the signal method has set the waitStatus to 0 / / the signal method will transfer the node to the blocking queue, but it may not be finished yet, and the spin is waiting for it to complete / / of course, this kind of thing is still relatively rare: after the signal call, before the transfer is completed, there is an interruption of while (! isOnSyncQueue (node)) Thread.yield () Return false;}

Again, even if an interrupt occurs, the node will still move to the blocking queue.

At this point, everyone should know how the while loop exited. Either interrupted or transferred successfully.

This depicts a scenario where there is a thread that is lined up behind the conditional queue, but because it is interrupted, it will be awakened, and then it will find that it is not the one being signal, but it will take the initiative to enter the blocking queue.

6. Acquire exclusive lock

After the while loop comes out, here is the code:

If (acquireQueued (node, savedState) & & interruptMode! = THROW_IE) interruptMode = REINTERRUPT

After the while comes out, we make sure that the node has entered the blocking queue and is ready to acquire the lock.

Here, the first parameter node of acquireQueued (node, savedState) has entered the queue through enq (node). The parameter savedState is the state before the lock was released. When this method returns, it acquires the lock on behalf of the current thread, and state = = savedState.

Note that as we said earlier, blocking queues are entered with or without interruptions, and the return value of acquireQueued (node, savedState) represents whether the thread is interrupted or not. If true is returned, it means that it has been interrupted, and interruptMode! = THROW_IE, which means that the interruption occurred before signal. Here, interruptMode is set to REINTERRUPT, which is used to interrupt again later.

Continue to go down:

If (node.nextWaiter! = null) / / clean up if cancelled unlinkCancelledWaiters (); if (interruptMode! = 0) reportInterruptAfterWait (interruptMode)

In a meticulous spirit, let's talk about how to satisfy node.nextWaiter! = null. As I said earlier, signal will transfer the node to the blocking queue, and one step is node.nextWaiter = null, which will disconnect the node from the conditional queue.

However, in the case of an interruption, was it before or after the signal? In this part, I also introduced that if the signal is interrupted before, you also need to transfer the node to the blocking queue. When this part is transferred, node.nextWaiter = null is not set.

As we said before, if a node cancels, the unlinkCancelledWaiters method will also be called, and this is it.

7. Processing interrupt state

At this point, we can finally talk about the use of this interruptMode.

Do nothing, never be interrupted

The THROW_IE:await method throws an InterruptedException exception because it represents an interrupt during await ()

REINTERRUPT: re-interrupts the current thread because it represents an interruption that occurs later in await (), but not during signal ()

Private void reportInterruptAfterWait (int interruptMode) throws InterruptedException {if (interruptMode = THROW_IE) throw new InterruptedException (); else if (interruptMode = = REINTERRUPT) selfInterrupt ();}

This part of the interrupted state should be understood by everyone. If you don't understand, just read it a few more times.

* await with timeout mechanism

After the previous seven steps, the entire ConditionObject class is basically analyzed, followed by a brief analysis of the await method with timeout mechanism.

Public final long awaitNanos (long nanosTimeout) throws InterruptedExceptionpublic final boolean awaitUntil (Date deadline) throws InterruptedExceptionpublic final boolean await (long time, TimeUnit unit) throws InterruptedException

All three methods are similar, so let's pick one out and have a look:

Public final boolean await (long time, TimeUnit unit) throws InterruptedException {/ / wait for so many nanoseconds long nanosTimeout = unit.toNanos (time); if (Thread.interrupted ()) throw new InterruptedException (); Node node = addConditionWaiter (); int savedState = fullyRelease (node); / / current time + waiting time = expiration time final long deadline = System.nanoTime () + nanosTimeout / / used to return whether await timed out boolean timedout = false; int interruptMode = 0; while (! isOnSyncQueue (node)) {/ / if (nanosTimeout = spinForTimeoutThreshold) LockSupport.parkNanos (this, nanosTimeout); if ((interruptMode = checkInterruptWhileWaiting (node))! = 0) break; / / get the remaining time nanosTimeout = deadline-System.nanoTime () } if (acquireQueued (node, savedState) & & interruptMode! = THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter! = null) unlinkCancelledWaiters (); if (interruptMode! = 0) reportInterruptAfterWait (interruptMode); return! timedout;}

The idea of timeout is simple. The await without the timeout parameter is park, and then wait for someone else to wake up. Now it is time to call the parkNanos method to sleep for a specified time, wake up and determine whether the signal has been called, but there is no timeout, otherwise it will be a timeout. If you time out, transfer to the blocking queue yourself, and then grab the lock.

* do not throw the await of InterruptedException

The last section about Condition.

Public final void awaitUninterruptibly () {Node node = addConditionWaiter (); int savedState = fullyRelease (node); boolean interrupted = false; while (! isOnSyncQueue (node)) {LockSupport.park (this); if (Thread.interrupted ()) interrupted = true;} if (acquireQueued (node, savedState) | | interrupted) selfInterrupt ();}

Very simple, paste the code and everyone will understand, I will not talk nonsense.

Cancellation queue of AbstractQueuedSynchronizer exclusive Lock

This article is about AbstractQueuedSynchronizer, but it seems that Condition has said too much, so hurry up and pull your mind back.

Next, I would like to talk about how to eliminate the competition for locks.

As mentioned in the last article, the most important method is this, in which we need to find the answer:

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);}}

First of all, when it comes to this method, the node must join the queue successfully.

I posted the parkAndCheckInterrupt () code here:

Private final boolean parkAndCheckInterrupt () {LockSupport.park (this); return Thread.interrupted ();}

These two pieces of code are linked to see if it is clear.

If we want to unqueue one thread, we need to interrupt it in another thread. For example, if a thread calls lock () and doesn't return for a long time, I want to interrupt it. Once interrupted, the thread wakes up from LockSupport.park (this);, and then Thread.interrupted (); returns true.

We found a problem that even if the interrupt wakes up the thread, it just sets interrupted = true and moves on to the next loop. Also, because Thread.interrupted (); clears the interrupt state, the second time you enter parkAndCheckInterrupt, the return will be false.

So, we need to see that in this method, interrupted is just used to record whether an interrupt has occurred, and then it is used to return the value of the method, and nothing else is done.

So, let's see how the outer method handles the case where acquireQueued returns false.

Public final void acquire (int arg) {if (! tryAcquire (arg) & & acquireQueued (addWaiter (Node.EXCLUSIVE), arg) selfInterrupt ();} static void selfInterrupt () {Thread.currentThread (). Interrupt ();}

So, the way the lock () method handles interrupts is that if you interrupt, I grab the lock or grab the lock, it almost doesn't matter, but after I get the lock, I set the interrupt state of the thread without throwing any exceptions. After the caller acquires the lock, he or she can check to see if an interruption has occurred or ignore it.

Give me a dividing line. Do you feel cheated? I talked a lot about it, but it has nothing to do with cancellation.

Let's take a look at another lock method of ReentrantLock:

Public void lockInterruptibly () throws InterruptedException {sync.acquireInterruptibly (1);}

There is an extra throws InterruptedException in the method, and after laying the groundwork for so much knowledge in front of me, I will no longer be verbose here.

Public final void acquireInterruptibly (int arg) throws InterruptedException {if (Thread.interrupted ()) throw new InterruptedException (); if (! tryAcquire (arg)) doAcquireInterruptibly (arg);}

Go on inside:

Private void doAcquireInterruptibly (int arg) throws InterruptedException {final Node node = addWaiter (Node.EXCLUSIVE); boolean failed = true; try {for (;;) {final Node p = node.predecessor (); if (p = = head & & tryAcquire (arg)) {setHead (node); p.next = null; / / help GC failed = false Return;} if (shouldParkAfterFailedAcquire (p, node) & & parkAndCheckInterrupt ()) / / this is it. Once an exception occurs, the method ends immediately and an exception is thrown. / / instead of marking the return value of this method to represent the interrupt state / /, the exception is thrown directly, and the outer layer does not catch it, throwing it all the way to lockInterruptibly throw new InterruptedException ();}} finally {/ / if the exception is sent through InterruptedException, then failed is true if (failed) cancelAcquire (node);}}

Now that we're here, by the way, let's talk about the cancelAcquire method:

Private void cancelAcquire (Node node) {/ / Ignore if node doesn't exist if (node = = null) return; node.thread = null; / / Skip cancelled predecessors / / find a suitable precursor. In fact, all the canceled nodes in the queue in front of it are "please go out" Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; / / predNext is the apparent node to unsplice. CASes below will / / fail if not, in which case, we lost race vs another cancel / / or signal, so no further action is necessary. Node predNext = pred.next; / / Can use unconditional write instead of CAS here. / / After this atomic step, other Nodes can skip past us. / / Before, we are free of interference from other threads. Node.waitStatus = Node.CANCELLED; / / If we are the tail, remove ourselves. If (node = = tail & & compareAndSetTail (node, pred)) {compareAndSetNext (pred, predNext, null);} else {/ / If successor needs signal, try to set pred's next-link / / so it will get one. Otherwise wake it up to propagate. Int ws; if (pred! = head & & ((ws = pred.waitStatus) = = Node.SIGNAL | (ws)

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