JAVA并发编程学习笔记之AQS源码分析(获取与释放)

JAVA并发编程中AQS是什么? /独占方式。尝试获取资源,成功则返回true,失败则返回false。//独占方式。尝试释放资源,成功则返回true,失败则返回false。//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。//共享方式。尝试释放资源,成功则返回true,失败则返回false。//该线程是否正在独占资源。只有用到condition才需要去实现它。 阅读详情


目录(?)[+]

同步状态

AQS采用的是CLH队列,CLH队列是由一个一个结点构成的,前面提到结点中有一个状态位,这个状态位与线程状态密切相关,这个状态位(waitStatus)是一个32位的整型常量,它的取值如下:

[java]  view plain  copy
 print ?
  1. static final int CANCELLED =  1;  
  2. static final int SIGNAL    = -1;  
  3. static final int CONDITION = -2;  
  4. static final int PROPAGATE = -3;  
下面解释一下每个值的含义

CANCELLED:因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态。处于这种状态的结点会被踢出队列,被GC回收;

SIGNAL:表示这个结点的继任结点被阻塞了,到时需要通知它;

CONDITION:表示这个结点在条件队列中,因为等待某个条件而被阻塞;

PROPAGATE:使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播;

0:None of the above,新结点会处于这种状态。


获取

AQS中比较重要的两个操作是获取和释放,以下是各种获取操作:

[java]  view plain  copy
 print ?
  1. public final void acquire(int arg);  
  2. public final void acquireInterruptibly(int arg);  
  3. public final void acquireShared(int arg);  
  4. public final void acquireSharedInterruptibly(int arg);  
  5. protected boolean tryAcquire(int arg);   
  6. protected int tryAcquireShared(int arg);  
  7. public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException;  
  8. public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException;       
获取操作的流程图如下:

1、如果尝试获取锁成功整个获取操作就结束,否则转到2. 尝试获取锁是通过方法tryAcquire来实现的,AQS中并没有该方法的具体实现,只是简单地抛出一个不支持操作异常,在AQS简介中谈到tryAcquire有很多实现方法,这里不再细化,只需要知道如果获取锁成功该方法返回true即可;

2、如果获取锁失败,那么就创建一个代表当前线程的结点加入到等待队列的尾部,是通过addWaiter方法实现的,来看该方法的具体实现:

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Creates and enqueues node for current thread and given mode. 
  3.  * 
  4.  * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared 
  5.  * @return the new node 
  6.  */  
  7. private Node addWaiter(Node mode) {  
  8.     Node node = new Node(Thread.currentThread(), mode);  
  9.     // Try the fast path of enq; backup to full enq on failure  
  10.     Node pred = tail;  
  11.     if (pred != null) {  
  12.         node.prev = pred;  
  13.         if (compareAndSetTail(pred, node)) {  
  14.             pred.next = node;  
  15.             return node;  
  16.         }  
  17.     }  
  18.     enq(node);  
  19.     return node;  
  20. }  
该方法创建了一个独占式结点,然后判断队列中是否有元素,如果有(pred!=null)就设置当前结点为队尾结点,返回;

如果没有元素(pred==null),表示队列为空,走的是入队操作

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Inserts node into queue, initializing if necessary. See picture above. 
  3.  * @param node the node to insert 
  4.  * @return node's predecessor 
  5.  */  
  6. private Node enq(final Node node) {  
  7.     for (;;) {  
  8.         Node t = tail;  
  9.         if (t == null) { // Must initialize  
  10.             if (compareAndSetHead(new Node()))  
  11.                 tail = head;  
  12.         } else {  
  13.             node.prev = t;  
  14.             if (compareAndSetTail(t, node)) {  
  15.                 t.next = node;  
  16.                 return t;  
  17.             }  
  18.         }  
  19.     }  
  20. }  
enq方法采用的是变种CLH算法,先看头结点是否为空,如果为空就创建一个傀儡结点,头尾指针都指向这个傀儡结点,这一步只会在队列初始化时会执行;

如果头结点非空,就采用CAS操作将当前结点插入到头结点后面,如果在插入的时候尾结点有变化,就将尾结点向后移动直到移动到最后一个结点为止,然后再把当前结点插入到尾结点后面,尾指针指向当前结点,入队成功。

3、将新加入的结点放入队列之后,这个结点有两种状态,要么获取锁,要么就挂起,如果这个结点不是头结点,就看看这个结点是否应该挂起,如果应该挂起,就挂起当前结点,是否应该挂起是通过shouldParkAfterFailedAcquire方法来判断的

[java]  view plain  copy
 print ?
  1. /** 
  2.     * Checks and updates status for a node that failed to acquire. 
  3.     * Returns true if thread should block. This is the main signal 
  4.     * control in all acquire loops.  Requires that pred == node.prev 
  5.     * 
  6.     * @param pred node's predecessor holding status 
  7.     * @param node the node 
  8.     * @return {@code true} if thread should block 
  9.     */  
  10.    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {  
  11.        int ws = pred.waitStatus;  
  12.        if (ws == Node.SIGNAL)  
  13.            /* 
  14.             * This node has already set status asking a release 
  15.             * to signal it, so it can safely park. 
  16.             */  
  17.            return true;  
  18.        if (ws > 0) {  
  19.            /* 
  20.             * Predecessor was cancelled. Skip over predecessors and 
  21.             * indicate retry. 
  22.             */  
  23.            do {  
  24.                node.prev = pred = pred.prev;  
  25.            } while (pred.waitStatus > 0);  
  26.            pred.next = node;  
  27.        } else {  
  28.            /* 
  29.             * waitStatus must be 0 or PROPAGATE.  Indicate that we 
  30.             * need a signal, but don't park yet.  Caller will need to 
  31.             * retry to make sure it cannot acquire before parking. 
  32.             */  
  33.            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  
  34.        }  
  35.        return false;  
  36.    }  
该方法首先检查前趋结点的waitStatus位,如果为SIGNAL,表示前趋结点会通知它,那么它可以放心大胆地挂起了;

如果前趋结点是一个被取消的结点怎么办呢?那么就向前遍历跳过被取消的结点,直到找到一个没有被取消的结点为止,将找到的这个结点作为它的前趋结点,将找到的这个结点的waitStatus位设置为SIGNAL,返回false表示线程不应该被挂起。
上面谈的不是头结点的情况决定是否应该挂起,是头结点的情况呢?

是头结点的情况,当前线程就调用tryAcquire尝试获取锁,如果获取成功就将头结点设置为当前结点,返回;如果获取失败就循环尝试获取锁,直到获取成功为止。整个acquire过程就分析完了。


释放

释放操作有以下方法:

[java]  view plain  copy
 print ?
  1. public final boolean release(int arg);   
  2. protected boolean tryRelease(int arg);   
  3. protected boolean tryReleaseShared(int arg);   

下面看看release方法的实现过程


1、release过程比acquire要简单,首先调用tryRelease释放锁,如果释放失败,直接返回;

2、释放锁成功后需要唤醒继任结点,是通过方法unparkSuccessor实现的

[java]  view plain  copy
 print ?
  1. /** 
  2.     * Wakes up node's successor, if one exists. 
  3.     * 
  4.     * @param node the node 
  5.     */  
  6.    private void unparkSuccessor(Node node) {  
  7.        /* 
  8.         * If status is negative (i.e., possibly needing signal) try 
  9.         * to clear in anticipation of signalling.  It is OK if this 
  10.         * fails or if status is changed by waiting thread. 
  11.         */  
  12.        int ws = node.waitStatus;  
  13.        if (ws < 0)  
  14.            compareAndSetWaitStatus(node, ws, 0);  
  15.   
  16.        /* 
  17.         * Thread to unpark is held in successor, which is normally 
  18.         * just the next node.  But if cancelled or apparently null, 
  19.         * traverse backwards from tail to find the actual 
  20.         * non-cancelled successor. 
  21.         */  
  22.        Node s = node.next;  
  23.        if (s == null || s.waitStatus > 0) {  
  24.            s = null;  
  25.            for (Node t = tail; t != null && t != node; t = t.prev)  
  26.                if (t.waitStatus <= 0)  
  27.                    s = t;  
  28.        }  
  29.        if (s != null)  
  30.            LockSupport.unpark(s.thread);  
  31.    }  
1、node参数传进来的是头结点,首先检查头结点的waitStatus位,如果为负,表示头结点还需要通知后继结点,这里不需要头结点去通知后继,因此将该该标志位清0.

2、然后查看头结点的下一个结点,如果下一个结点不为空且它的waitStatus<=0,表示后继结点没有被取消,是一个可以唤醒的结点,于是唤醒后继结点返回;如果后继结点为空或者被取消了怎么办?寻找下一个可唤醒的结点,然后唤醒它返回。

这里并没有从头向尾寻找,而是相反的方向寻找,为什么呢?

因为在CLH队列中的结点随时有可能被中断,被中断的结点的waitStatus设置为CANCEL,而且它会被踢出CLH队列,如何个踢出法,就是它的前趋结点的next并不会指向它,而是指向下一个非CANCEL的结点,而它自己的next指针指向它自己。一旦这种情况发生,如何从头向尾方向寻找继任结点会出现问题,因为一个CANCEL结点的next为自己,那么就找不到正确的继任接点。

有的人又会问了,CANCEL结点的next指针为什么要指向它自己,为什么不指向真正的next结点?为什么不为NULL?

第一个问题的答案是这种被CANCEL的结点最终会被GC回收,如果指向next结点,GC无法回收。

对于第二个问题的回答,JDK中有这么一句话: The next field of cancelled nodes is set to point to the node itself instead of null, to make life easier for isOnSyncQueue.大至意思是为了使isOnSyncQueue方法更新简单。isOnSyncQueue方法判断一个结点是否在同步队列,实现如下:

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Returns true if a node, always one that was initially placed on 
  3.  * a condition queue, is now waiting to reacquire on sync queue. 
  4.  * @param node the node 
  5.  * @return true if is reacquiring 
  6.  */  
  7. final boolean isOnSyncQueue(Node node) {  
  8.     if (node.waitStatus == Node.CONDITION || node.prev == null)  
  9.         return false;  
  10.     if (node.next != null) // If has successor, it must be on queue  
  11.         return true;  
  12.     /* 
  13.      * node.prev can be non-null, but not yet on queue because 
  14.      * the CAS to place it on queue can fail. So we have to 
  15.      * traverse from tail to make sure it actually made it.  It 
  16.      * will always be near the tail in calls to this method, and 
  17.      * unless the CAS failed (which is unlikely), it will be 
  18.      * there, so we hardly ever traverse much. 
  19.      */  
  20.     return findNodeFromTail(node);  
  21. }  
如果一个结点next不为空,那么它在同步队列中,如果CANCEL结点的后继为空那么CANCEL结点不在同步队列中,这与事实相矛盾。

因此将CANCEL结点的后继指向它自己是合理的选择。


参考资料:

目录(?)[+]

同步状态

AQS采用的是CLH队列,CLH队列是由一个一个结点构成的,前面提到结点中有一个状态位,这个状态位与线程状态密切相关,这个状态位(waitStatus)是一个32位的整型常量,它的取值如下:

[java]  view plain  copy
 print ?
  1. static final int CANCELLED =  1;  
  2. static final int SIGNAL    = -1;  
  3. static final int CONDITION = -2;  
  4. static final int PROPAGATE = -3;  
下面解释一下每个值的含义

CANCELLED:因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,只能保持取消状态不变,不能转换为其他状态。处于这种状态的结点会被踢出队列,被GC回收;

SIGNAL:表示这个结点的继任结点被阻塞了,到时需要通知它;

CONDITION:表示这个结点在条件队列中,因为等待某个条件而被阻塞;

PROPAGATE:使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播;

0:None of the above,新结点会处于这种状态。


获取

AQS中比较重要的两个操作是获取和释放,以下是各种获取操作:

[java]  view plain  copy
 print ?
  1. public final void acquire(int arg);  
  2. public final void acquireInterruptibly(int arg);  
  3. public final void acquireShared(int arg);  
  4. public final void acquireSharedInterruptibly(int arg);  
  5. protected boolean tryAcquire(int arg);   
  6. protected int tryAcquireShared(int arg);  
  7. public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException;  
  8. public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException;       
获取操作的流程图如下:

1、如果尝试获取锁成功整个获取操作就结束,否则转到2. 尝试获取锁是通过方法tryAcquire来实现的,AQS中并没有该方法的具体实现,只是简单地抛出一个不支持操作异常,在AQS简介中谈到tryAcquire有很多实现方法,这里不再细化,只需要知道如果获取锁成功该方法返回true即可;

2、如果获取锁失败,那么就创建一个代表当前线程的结点加入到等待队列的尾部,是通过addWaiter方法实现的,来看该方法的具体实现:

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Creates and enqueues node for current thread and given mode. 
  3.  * 
  4.  * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared 
  5.  * @return the new node 
  6.  */  
  7. private Node addWaiter(Node mode) {  
  8.     Node node = new Node(Thread.currentThread(), mode);  
  9.     // Try the fast path of enq; backup to full enq on failure  
  10.     Node pred = tail;  
  11.     if (pred != null) {  
  12.         node.prev = pred;  
  13.         if (compareAndSetTail(pred, node)) {  
  14.             pred.next = node;  
  15.             return node;  
  16.         }  
  17.     }  
  18.     enq(node);  
  19.     return node;  
  20. }  
该方法创建了一个独占式结点,然后判断队列中是否有元素,如果有(pred!=null)就设置当前结点为队尾结点,返回;

如果没有元素(pred==null),表示队列为空,走的是入队操作

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Inserts node into queue, initializing if necessary. See picture above. 
  3.  * @param node the node to insert 
  4.  * @return node's predecessor 
  5.  */  
  6. private Node enq(final Node node) {  
  7.     for (;;) {  
  8.         Node t = tail;  
  9.         if (t == null) { // Must initialize  
  10.             if (compareAndSetHead(new Node()))  
  11.                 tail = head;  
  12.         } else {  
  13.             node.prev = t;  
  14.             if (compareAndSetTail(t, node)) {  
  15.                 t.next = node;  
  16.                 return t;  
  17.             }  
  18.         }  
  19.     }  
  20. }  
enq方法采用的是变种CLH算法,先看头结点是否为空,如果为空就创建一个傀儡结点,头尾指针都指向这个傀儡结点,这一步只会在队列初始化时会执行;

如果头结点非空,就采用CAS操作将当前结点插入到头结点后面,如果在插入的时候尾结点有变化,就将尾结点向后移动直到移动到最后一个结点为止,然后再把当前结点插入到尾结点后面,尾指针指向当前结点,入队成功。

3、将新加入的结点放入队列之后,这个结点有两种状态,要么获取锁,要么就挂起,如果这个结点不是头结点,就看看这个结点是否应该挂起,如果应该挂起,就挂起当前结点,是否应该挂起是通过shouldParkAfterFailedAcquire方法来判断的

[java]  view plain  copy
 print ?
  1. /** 
  2.     * Checks and updates status for a node that failed to acquire. 
  3.     * Returns true if thread should block. This is the main signal 
  4.     * control in all acquire loops.  Requires that pred == node.prev 
  5.     * 
  6.     * @param pred node's predecessor holding status 
  7.     * @param node the node 
  8.     * @return {@code true} if thread should block 
  9.     */  
  10.    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {  
  11.        int ws = pred.waitStatus;  
  12.        if (ws == Node.SIGNAL)  
  13.            /* 
  14.             * This node has already set status asking a release 
  15.             * to signal it, so it can safely park. 
  16.             */  
  17.            return true;  
  18.        if (ws > 0) {  
  19.            /* 
  20.             * Predecessor was cancelled. Skip over predecessors and 
  21.             * indicate retry. 
  22.             */  
  23.            do {  
  24.                node.prev = pred = pred.prev;  
  25.            } while (pred.waitStatus > 0);  
  26.            pred.next = node;  
  27.        } else {  
  28.            /* 
  29.             * waitStatus must be 0 or PROPAGATE.  Indicate that we 
  30.             * need a signal, but don't park yet.  Caller will need to 
  31.             * retry to make sure it cannot acquire before parking. 
  32.             */  
  33.            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);  
  34.        }  
  35.        return false;  
  36.    }  
该方法首先检查前趋结点的waitStatus位,如果为SIGNAL,表示前趋结点会通知它,那么它可以放心大胆地挂起了;

如果前趋结点是一个被取消的结点怎么办呢?那么就向前遍历跳过被取消的结点,直到找到一个没有被取消的结点为止,将找到的这个结点作为它的前趋结点,将找到的这个结点的waitStatus位设置为SIGNAL,返回false表示线程不应该被挂起。
上面谈的不是头结点的情况决定是否应该挂起,是头结点的情况呢?

是头结点的情况,当前线程就调用tryAcquire尝试获取锁,如果获取成功就将头结点设置为当前结点,返回;如果获取失败就循环尝试获取锁,直到获取成功为止。整个acquire过程就分析完了。


释放

释放操作有以下方法:

[java]  view plain  copy
 print ?
  1. public final boolean release(int arg);   
  2. protected boolean tryRelease(int arg);   
  3. protected boolean tryReleaseShared(int arg);   

下面看看release方法的实现过程


1、release过程比acquire要简单,首先调用tryRelease释放锁,如果释放失败,直接返回;

2、释放锁成功后需要唤醒继任结点,是通过方法unparkSuccessor实现的

[java]  view plain  copy
 print ?
  1. /** 
  2.     * Wakes up node's successor, if one exists. 
  3.     * 
  4.     * @param node the node 
  5.     */  
  6.    private void unparkSuccessor(Node node) {  
  7.        /* 
  8.         * If status is negative (i.e., possibly needing signal) try 
  9.         * to clear in anticipation of signalling.  It is OK if this 
  10.         * fails or if status is changed by waiting thread. 
  11.         */  
  12.        int ws = node.waitStatus;  
  13.        if (ws < 0)  
  14.            compareAndSetWaitStatus(node, ws, 0);  
  15.   
  16.        /* 
  17.         * Thread to unpark is held in successor, which is normally 
  18.         * just the next node.  But if cancelled or apparently null, 
  19.         * traverse backwards from tail to find the actual 
  20.         * non-cancelled successor. 
  21.         */  
  22.        Node s = node.next;  
  23.        if (s == null || s.waitStatus > 0) {  
  24.            s = null;  
  25.            for (Node t = tail; t != null && t != node; t = t.prev)  
  26.                if (t.waitStatus <= 0)  
  27.                    s = t;  
  28.        }  
  29.        if (s != null)  
  30.            LockSupport.unpark(s.thread);  
  31.    }  
1、node参数传进来的是头结点,首先检查头结点的waitStatus位,如果为负,表示头结点还需要通知后继结点,这里不需要头结点去通知后继,因此将该该标志位清0.

2、然后查看头结点的下一个结点,如果下一个结点不为空且它的waitStatus<=0,表示后继结点没有被取消,是一个可以唤醒的结点,于是唤醒后继结点返回;如果后继结点为空或者被取消了怎么办?寻找下一个可唤醒的结点,然后唤醒它返回。

这里并没有从头向尾寻找,而是相反的方向寻找,为什么呢?

因为在CLH队列中的结点随时有可能被中断,被中断的结点的waitStatus设置为CANCEL,而且它会被踢出CLH队列,如何个踢出法,就是它的前趋结点的next并不会指向它,而是指向下一个非CANCEL的结点,而它自己的next指针指向它自己。一旦这种情况发生,如何从头向尾方向寻找继任结点会出现问题,因为一个CANCEL结点的next为自己,那么就找不到正确的继任接点。

有的人又会问了,CANCEL结点的next指针为什么要指向它自己,为什么不指向真正的next结点?为什么不为NULL?

第一个问题的答案是这种被CANCEL的结点最终会被GC回收,如果指向next结点,GC无法回收。

对于第二个问题的回答,JDK中有这么一句话: The next field of cancelled nodes is set to point to the node itself instead of null, to make life easier for isOnSyncQueue.大至意思是为了使isOnSyncQueue方法更新简单。isOnSyncQueue方法判断一个结点是否在同步队列,实现如下:

[java]  view plain  copy
 print ?
  1. /** 
  2.  * Returns true if a node, always one that was initially placed on 
  3.  * a condition queue, is now waiting to reacquire on sync queue. 
  4.  * @param node the node 
  5.  * @return true if is reacquiring 
  6.  */  
  7. final boolean isOnSyncQueue(Node node) {  
  8.     if (node.waitStatus == Node.CONDITION || node.prev == null)  
  9.         return false;  
  10.     if (node.next != null) // If has successor, it must be on queue  
  11.         return true;  
  12.     /* 
  13.      * node.prev can be non-null, but not yet on queue because 
  14.      * the CAS to place it on queue can fail. So we have to 
  15.      * traverse from tail to make sure it actually made it.  It 
  16.      * will always be near the tail in calls to this method, and 
  17.      * unless the CAS failed (which is unlikely), it will be 
  18.      * there, so we hardly ever traverse much. 
  19.      */  
  20.     return findNodeFromTail(node);  
  21. }  
如果一个结点next不为空,那么它在同步队列中,如果CANCEL结点的后继为空那么CANCEL结点不在同步队列中,这与事实相矛盾。

因此将CANCEL结点的后继指向它自己是合理的选择。


参考资料:

Java并发编程之AQS AQS是AbstractQueueSynchronizer是缩写,其意思就是抽象队列同步器。原理就是通过一个FIFO队列维护一个state同步状态值,只需要继承该抽象类重写对应的方法便可实现一套线程同步机制。AQS抽象类在java.util.concurrent.locks包下定义。如上图所示,AQS属于一个抽象类,主要是用来构建锁结构以及同步器的一个抽象类。AQS继承了AbstractOwnableSynchronizer,其作用为设置和获取独占锁的拥有者线程方法。 阅读详情

相关推荐

告别云端依赖:手把手教你在Windows 10/11上离线部署WhisperDesktop(含模型下载避坑指南)

本文详细介绍了如何在Windows 10/11上离线部署WhisperDesktop,包括硬件要求、模型下载、环境配置和实战应用。通过本地部署,用户可以实现高效的语音转文字功能,无需依赖云端服务,特别适合注重数据隐私和需要离线工作的场景。

weixin_33736048的博客 322

java线程池 -- 大话笔记

java线程池的概念 java线程池知识点扫盲 java线程池的模式 java线程池源码解析 java线程池概念 我们知道,java最大的特性在于其对多线程以及并发性的友好支持,但线程的创建是和我们的cpu有关系的,持续不断的创建势必会带来更多的开销,当我们有多个请求并发而来,且访问的时间很短,这就势必会造成我们cpu的压力,而java线程池就是为了解决这个问题而产生的,利用已有的线程来处理多个任

ooiuy450的专栏 472

LVI-SAM实战:如何在Ubuntu20.04上快速复现激光-视觉-惯性里程计系统

本文详细解析了在Ubuntu 20.04环境下部署和优化LVI-SAM激光-视觉-惯性里程计系统的全流程。从环境配置、依赖安装到源码编译和实战调参,提供了完整的解决方案和性能优化策略,帮助开发者快速实现厘米级精度的实时位姿估计。

m3n5b7v8c9x的博客 1047

并发编程之——AQS原理和阻塞队列变化

1、AQS简介 2、源码分析 2.1 线程阻塞 2.2 线程唤醒 1、AQS简介 AQS全名:AbstractQueuedSynchronizer,它就是Java的一个抽象类,它的出现是为了解决多线程竞争共享资源而引发的安全问题,细致点说AQS具备一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中,队列是双向队列。 常用...

做最亮的星星 3444

别再只用GWR了!用Python的mgtwr包搞定时空地理加权回归(GTWR)实战

本文详细介绍了如何使用Python的mgtwr包进行时空地理加权回归(GTWR)实战,突破传统GWR模型的局限。通过核心差异解析、实战全流程演示和进阶技巧,帮助读者掌握GTWR在房价波动、污染物扩散等时空数据分析中的应用,提升模型预测精度。

weixin_30899023的博客 341

VH6501干扰仪的使用

触发模式方法备注External Trigger(外部触发)使用VH6501的I/O口作为触发源Frame Trigger(报文触发)设定被干扰报文的触发位置,并发送干扰位序列/报文software Trigger(软件触发)配置好干扰序列后直接发送Error Frame Trigger(错误帧触发)基于错误帧标识位为触发条件触发干扰Direction Trigger(发送方向触发)根据TX/RX发送/接收方作为触发条件Inverted Trigger(反向触发)

帮助100万人颠覆负面潜意识,实现人生蜕变,培养1万名潜意识开发导师 9051

深入JVM锁机制2-Lock

JVM Lock, ReentrantLock,CLH,CAS,自旋锁,偏向锁

老码农的专栏 3万+

PP生产订单的BADI增强 WORKORDER_UPDATE

我们的项目需求是: 1、 保存时检查工单BOM是否存在物料号相同的行项目,如果有则提示“物料& 重复,请检查工单BOM” 系统增强出口:PPCO0001,检查组件行项目是否有重复物料项 开始以为的增强增强看一下是用PPCO0001还是PPCO0007 后来找到了badi WORKORDER_UPDATE IF_EX_WORKORDER_UPDATE~BEFORE_UPDATE

ERP运维咨询(目标和成功) 4635

第三章 显式锁和AQS笔记

谈到并发不得不谈ReentrantLock,而谈到ReentrantLock不得不谈AbstractQueuedSynchronizer(AQS)。类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体vo

hankin的博客 363

关于RT-Thread中PWM无法正常输出的学习

按照官方的PWM配置,并没有输出PWM,网上有比较多的办法,试了很多,有些还是不能实现输出,这里做个记录。记录自己完成PWM输出的过程,以及修改的地方。 关于STM32CubeMX配置就不多说了,网上有很多。 首先必定是按照流程来,一定一定按照流程来!!! 需要注意的是加上通道数!!!! 这是流程上没有的,也是RTT的一点小BUG吧 配上board.c中添加的代码,我用的是PA2定时2的通道3,需要使用的朋友修改对应定时器和GPIO! void HAL_TIM_Base_MspInit(TIM_Han

weixin_42566346的博客 3554

VESC电调高级调参指南:电机校准与性能优化实战解析

本文详细解析了VESC电调的高级调参方法,包括电机校准与性能优化实战技巧。从基础参数设置到高级机械参数优化,再到动态性能调优,提供了全面的调参指南。特别针对电机类型、功率设置、电池参数配置等关键环节给出实用建议,帮助用户充分发挥VESC电调的性能潜力。

weixin_30772105的博客 196

队列同步器(AQS)详解

Java自定义同步组件的设计分析、设计思路与源码分析、AQS(AbstractQueuedSynchronizer)结构与原理

void 1万+

[工作流节点24] 外部用户审批的实现方式:让客户、供应商也能参与流程决策

摘要: 明道云支持通过三种方式实现外部用户审批:1)外部表单审批(推荐方案),通过公开表单让外部用户提交审批意见并自动触发流程;2)链接参数审批,通过含参数的URL实现一键确认;3)API/Webhook审批,适用于系统集成场景。三种方案均无需外部用户登录,支持记录操作日志和安全审计。典型应用包括客户确认报价、供应商发货确认等,实现内外流程无缝衔接。建议根据业务复杂度选择:无代码需求用外部表单,轻量审批用链接参数,系统对接用Webhook。

每日出拳老爷子的博客 347

JAVA并发编程学习笔记之AQS源码分析(获取与释放)(r)

同步状态 AQS采用的是CLH队列,CLH队列是由一个一个结点构成的,前面提到结点中有一个状态位,这个状态位与线程状态密切相关,这个状态位(waitStatus)是一个32位的整型常量,它的取值如下: [java] view plain copy  print? static final int CANCELLED =  1;   stat

XINJing的专栏 436

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

Java 并发编程之AQS

AbstractQueuedSynchronizer (AQS) 是 Java 并发编程中的一个核心框架,广泛用于构建锁和其他同步器(如信号量、读写锁等)。它是 java.util.concurrent.locks 包的一部分。AQS 的设计目的是简化并发同步器的实现。理解 AQS 对于深入理解 Java 并发编程非常重要。

微笑听雨 6763

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

爆肝万字,AQS 源码分析!

最近一周在写 AQS 源码分析,AQS 属于并发的核心内容,很多同步器都基于 AQS 实现。可能很多同学并不太了解 AQS 学习到什么程度好,以及 AQS 中的 Node 节点状态是在是太复杂,看的眼花缭乱!通过这一周看 AQS 源码的经验,我觉得学习 AQS 只要把它内部的执行流程捋顺就可以了。AQS 主要是管理线程的入队、出队,以及队列中线程等待获取资源这一过程,所以自己在学习的过程中也有必要画画图,来看一下在入队、出队的时候,节点的状态是如何变化。通过画图,可以很好地理清思路。

qq_45260619的博客 1326

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

AQS源码分析之独占式获取锁和释放锁

AQS源码分析之独占式获取锁和释放锁 队列同步器AbstractQueuedSynchronizer(以下简称AQS),是用来构建锁或者其他同步组件的基础框架,ReentrantLock、ReentrantReadWriteLock和CountDownLatch等并发工具类底层都是通过AQS实现的。 AQS的数据结构 同步状态 AQS内部使用一个被volatile关键字修饰的state属性来表示同步状态,并提供了下面三个方法来对这个同步状态进行操作: private volatile int state;

morris 2万+

Java AQS:并发编程的核心机制

AQS 即 AbstractQueuedSynchronizer,是 Java 并发包中的一个抽象类,它提供了一种实现同步器的框架。同步器可以用于实现锁、信号量、条件变量等并发工具。AQS 维护了一个等待队列,用于存储等待获取同步状态的线程。当一个线程尝试获取同步状态失败时,它会被加入到等待队列中,等待其他线程释放同步状态。当一个线程释放同步状态时,AQS 会唤醒等待队列中的一个线程,使其有机会获取同步状态。

果酱 の 博客 1254

【Java并发编程】AQS(4)——共享锁的获取与释放

今天来说下共享锁的获取与释放,建议大家在看这篇文章之前,先将我写的关于独占锁的文章看一下,其中涉及了许多重复的方法,在这篇文章中就不会再次讲解了。好了,我们先来看共享锁的获取吧 一. 共享锁的获取 在AQS中共享锁的获取一共有三个方法,今天主要讲第一个 acquireShared:不响应中断获取锁 acquireSharedInterruptibly:响应中...

哈喽羊 876

图解ReentrantLock的基石AQS-独占锁的获取与释放

的。

小牛呼噜噜的博客 997

Java 并发编程:AQS 框架原理与应用

AQS 是一个抽象类,位于包中。它通过一个 int 类型的状态变量(state)来表示同步状态,并使用一个 FIFO 队列来管理等待的线程。AQS 的核心思想是通过 CAS(Compare - And - Swap)操作来原子性地更新状态变量,从而实现多线程之间的同步。AQS 框架是 Java 并发编程中的一个核心组件,它为构建锁和同步器提供了强大的基础。通过使用 AQS,开发者可以方便地实现各种复杂的同步器,如 ReentrantLock、Semaphore、CountDownLatch 等。

潜意识Java的博客 1034

深入解析AQS原理:Java并发编程的核心

AQS(AbstractQueuedSynchronizer)是Java并发包()的核心组件,用于构建锁和其他同步器的基础框架。它通过一个FIFO队列管理线程的排队和唤醒机制,提供了独占模式和共享模式的同步功能。AQS的核心功能🧩同步状态管理:通过state变量管理锁的状态🛠️线程排队:通过FIFO队列管理等待线程🔒条件支持:支持条件变量(Condition)的实现AQS的应用Semaphore。

KBkongbaiKB的博客 1184
上一篇: Java并发编程实战---- AQS(二):获取锁、释放锁
下一篇: 阿里菜鸟java岗面试
大树叶
博客等级 码龄12年 1171粉丝 664原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值