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

What is the use of AQS, the core class of Java JUC

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

Share

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

This article mainly introduces "what is the use of AQS, the core class of Java JUC?". In the daily operation, I believe that many people have doubts about the use of AQS, the core class of Java JUC. The editor consulted all kinds of materials and sorted out simple and easy-to-use methods of operation. I hope it will be helpful to answer the doubts of "what is the use of AQS, the core class of Java JUC?" Next, please follow the editor to study!

Brief introduction

When analyzing the source code of Java concurrent package java.util.concurrent, it is necessary to understand the abstract class AbstractQueuedSynchronizer (hereinafter abbreviated as AQS), because it is the basic tool class for Java concurrent package and the basis for implementing ReentrantLock, CountDownLatch, Semaphore, FutureTask and other classes.

Google AbstractQueuedSynchronizer, we can find a lot of introduction to AQS, but many are not clear, because most of the articles do not clarify some of the key details.

This article will start from the fair lock source code of ReentrantLock, analyze how the AbstractQueuedSynchronizer class works, and hope to provide you with some simple help.

State the following points:

This article is a bit long, but still quite simple, mainly for beginners of concurrent programming, or developers who want to read the source code of the Java concurrency package. For beginners, it may take hours to fully understand, but it's definitely worth the time.

Source code environment JDK1.7 (1.8 no change), see do not understand or have doubts about the part, it is best to open the source code to see. The code of Doug Lea is really good.

This paper does not analyze the sharing mode, which can reduce a lot of burden on readers. The third article analyzes the sharing mode. And the condition part is not analyzed, so it should be said that it is easy to understand.

In this paper, we use the concept of ReentrantLock most frequently, which is incorrect in nature. Readers should know that AQS is not only used to implement reentrant locks, but also hope that readers can associate AQS usage scenarios with locks and reduce the pressure of reading.

There is only a slight difference between fair locks and unfair locks in ReentrantLock, which is introduced in the second article.

There is feedback from readers in the comment area that this article is directly unfriendly in code and should be equipped with more flow charts. This article does have this problem. But as an experienced person, I would like to tell you that for AQS, the form is really not important, the important thing is to explain the details clearly.

AQS structure

First, let's take a look at the attributes of AQS. Figure out these basic tricks and know what AQS is. After all, you can guess!

/ / header node, which you think of directly as the current thread holding the lock is probably the best understood tail node blocked by private transient volatile Node head;//. Each new node is inserted at the end, forming a linked list private transient volatile Node tail. / / this is the most important, representing the status of the current lock. 0 means it is not occupied. A value greater than 0 means that the thread holds the current lock / / this value can be greater than 1, because the lock can be reentered and 1private volatile int state is added every time it is reentered. / / represents the thread that currently holds an exclusive lock, to give the most important example, because the lock can be reentered / / reentrantLock.lock () can be called multiple times, so this is used each time to determine whether the current thread already has a lock / / if (currentThread = = getExclusiveOwnerThread ()) {state++} private transient Thread exclusiveOwnerThread; / / inherits from AbstractOwnableSynchronizer.

Well, it seems to be very simple, after all, there are only four attributes.

The wait queue for AbstractQueuedSynchronizer is shown below. Note that the queue mentioned in the later analysis process, that is, the blocking queue does not contain head, head, or head.

Each thread in the waiting queue is packaged as a Node instance, and the data structure is a linked list. Let's take a look at the source code:

Static final class Node {/ / identifies that the node is currently in shared mode static final Node SHARED = new Node (); / / identifies that the node is currently in exclusive mode static final Node EXCLUSIVE = null; / / = the following int constants are used for waitStatus = / * * waitStatus value to indicate thread has cancelled * / / code this thread cancels the scramble for the lock static final int CANCELLED = 1 / * * waitStatus value to indicate successor's thread needs unparking * / / the official description is that the thread corresponding to the successor node of the current node needs to be awakened static final int SIGNAL =-1; / * * waitStatus value to indicate thread is waiting on condition * / / this article does not analyze condition, so skip it. The next article will introduce this static final int CONDITION =-2 / * waitStatus value to indicate the next acquireShared should * unconditionally propagate * / / do not analyze the same, skip static final int PROPAGATE =-3 / / the value is 1,-1,-2,-3, or 0 (which we will talk about later) / / above. For the time being, you only need to know that if this value is greater than 0, the thread cancels the wait. / / ps: if you can't grab the lock for a long time, ReentrantLock can specify timeouot. Volatile int waitStatus; / / reference to the precursor node volatile Node prev; / / reference to the successor node volatile Node next; / / this is the thread volatile Thread thread;}

The data structure of Node is actually quite simple, which is just four attributes of thread + waitStatus + pre + next. We should first have this concept in mind.

The above is the basic knowledge, which will be used many times later. Just keep them in mind and keep this structure diagram in mind. Next, let's start with ReentrantLock's fair lock. Again, the blocking queue I'm talking about does not contain head nodes.

First, let's take a look at how ReentrantLock is used.

/ / I'll use a service concept in web development, public class OrderService {/ / use static, so that each thread gets the same lock. Of course, service in spring mvc is a singleton by default, don't worry about this private static ReentrantLock reentrantLock = new ReentrantLock (true); public void createOrder () {/ / for example, we only allow one thread to create an order reentrantLock.lock () at the same time. / / usually, lock is followed by the try statement try {/ / this code can only have one thread coming in at a time (the thread that acquired the lock), / / other threads block on the lock () method, waiting for the lock to be acquired Then enter / / execute the code.} finally {/ / release lock reentrantLock.unlock () }}}

ReentrantLock internally uses the inner class Sync to manage locks, so the real acquisition and release of locks is controlled by Sync's implementation class.

Abstract static class Sync extends AbstractQueuedSynchronizer {}

Sync has two implementations, NonfairSync (unfair lock) and FairSync (fair lock). Let's take a look at the FairSync section.

Public ReentrantLock (boolean fair) {sync = fair? New FairSync (): new NonfairSync ();} thread grab lock

A lot of people must start to dislike the above too much nonsense, let's follow the code, I won't talk nonsense.

Static final class FairSync extends Sync {private static final long serialVersionUID =-3000897897090466540L; / contention lock final void lock () {acquire (1);} / from the parent class AQS, I will post it here directly, the same will be done in the following analysis, there will be no reading pressure on the reader / / as we can see, this method, if tryAcquire (arg) returns true, it will be over. / / otherwise, the acquireQueued method will press the thread into the queue public final void acquire (int arg) {/ / at this time arg = = 1 / / first call tryAcquire (1). The name shows that this is just a try / / because it is possible to succeed directly, and there is no need to queue up. / / the meaning of fair lock is: no one holds a lock in the first place. There is no need to wait in the queue (both suspended and awakened) if (! tryAcquire (arg) & & / / tryAcquire (arg) did not succeed, so you need to suspend the current thread and put it in the blocking queue. AcquireQueued (addWaiter (Node.EXCLUSIVE), arg) {selfInterrupt ();}} / * * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. * / / an attempt is made to acquire the lock directly. The return value is boolean, indicating whether the lock is acquired / / true:1 is returned. No thread is waiting for the lock; 2. If you reenter the lock, the thread already holds the lock, so of course you can get protected final boolean tryAcquire (int acquires) {final Thread current = Thread.currentThread (); int c = getState (). / / state = = 0 No thread holds the lock if (c = = 0) {/ / although the lock is available at the moment, it is a fair lock. Since it is fair, you have to pay attention to first come, first served. / / see if anyone else has been waiting in the queue for a long time. If (! hasQueuedPredecessors () & & / / if there is no thread waiting, try it with CAS. If you succeed, you will get the lock. / / if it is not successful, it can only show one problem. At almost the same time, a thread took the lead = _ = / / because there was no one just now, I judged that compareAndSetState (0, acquires) {/ / got the lock here, mark it, and tell you that I am the one who occupied the lock setExclusiveOwnerThread (current). Return true;}} / / will enter this else if branch, indicating that it is re-entered and requires action: state=state+1 / / there is no concurrency problem here else if (current = = getExclusiveOwnerThread ()) {int nextc = c + acquires; if (nextc)

< 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // 如果到这里,说明前面的if和else if都没有返回true,说明没有获取到锁 // 回到上面一个外层调用方法继续看: // if (!tryAcquire(arg) // && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // selfInterrupt(); return false; } // 假设tryAcquire(arg) 返回false,那么代码将执行: // acquireQueued(addWaiter(Node.EXCLUSIVE), arg), // 这个方法,首先需要执行:addWaiter(Node.EXCLUSIVE) /** * Creates and enqueues node for current thread and given mode. * * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared * @return the new node */ // 此方法的作用是把线程包装成node,同时进入到队列中 // 参数mode此时是Node.EXCLUSIVE,代表独占模式 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure // 以下几行代码想把当前node加到链表的最后面去,也就是进到阻塞队列的最后 Node pred = tail; // tail!=null =>

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