首页 技术 正文
技术 2022年11月15日
0 收藏 305 点赞 4,195 浏览 9569 个字

(一)什么是AQS?

阅读java文档可以知道,AbstractQueuedSynchronizer是实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架,它是一个依靠单个原子 int 值来表示状态的大多数同步器的一个基础类。在jdk中他的实现的类有Semaphore,ReentrantLock,CountDownLatch,ReentrantReadWriteLock等等很多的实现。

(二)原理

  它通过实现一个volatile int state来维护线程的状态,并使用一个双向链表来维护多个线程的等待队列。一般将子类作为一个非公共的内部帮助器类。它存在有独享和共享两种模式,在独享模式下,当锁被占用时,其他线程试图获取锁一定不会成功,共享模式下其他线程获取锁可能会成功。(读写锁读-读不互斥,读-写,写-写互斥)

它提供了getState()setState(int) ,compareAndSetState(int, int) 三个方法来获取和修改单个原子状态的方法,在使用的是我,我们只需要重写以下几个方法即可,

(三)源码实现

acquire:  

  根据调用流程acquire-release来一步步分析独享模式的源码,共享模式会在后面的学习中补上,首先从acquire,锁的入口开始。

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
  1)tryAcquire(int)最少会执行一次尝试去获取资源,如果获取到锁,则返回true,否则放回false。
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
tryAcquire(int)在AQS源码中直接直接抛出了一个异常,这是因为我们前面说过AQS是一个框架,使用的时候是需要我们去实现自己的核心部分的,tryAcquire(int)这里调用的实际上是在使用时我们重写的方法,通过操作state
来进行我们自己的实现的。
  2)addWaiter(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 pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

这里的Node包含了当前线程本身以及线程的状态及以下基本信息,这里就直接看注释,我就不一一写出来了

/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3; /**
* Status field, taking on only the values:
* SIGNAL: 值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。
* CANCELLED: 值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化
* CONDITION: 值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁
      * PROPAGATE: 值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。

  这里特别说下一下0状态表示为初始化状态。

  addWaiter()方法创建一个当前线程节点,当尾节点不为空时,将当前节点的prev指向双向链表的尾节点,并将当前节点通过cas的方式设置为尾节点,将尾节点的next指向当前节点,成功将整个链表连起来,这就是将当前线程加入到队列的过程,返回当前创建的节点。当尾节点为空时,调用enq方法。

  enq(node)方法

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

  for()循环表示自旋cas,知道成功将节点添加到队列为止,如果此时的尾节点tail为空,则创建一个空标志性的节点作为head节点,并将尾节点t也指向它,当tail不为空时,就按照前面的正常的添加节点的方式将当前节点添加到队列尾部。添加成功,退出循环,返回当前节点。

  4)acquireQueued()使线程在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。

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

  当线程获取资源失败,并且已经进入了队列中了,那么此时的线程就需要等待了,只等待其他线程执行完成之后,再唤醒队列中的线程时,唤醒到该线程时,该线程才可以继续执行,failed标记线程是否拿到锁,interrupted标记线程是否被中断过。自旋获取锁,

  第一步,拿到当前的上一个节点,即前驱,如果上一个节点是头节点,则有资格去获取锁,可能是上一个线程释放锁之后轮到第二个线程,当有资格获取锁时,将当前节点设置成头节点,此时前面的一个节点已经出了队列了,并将前一个节点置为空,为了方便垃圾回收,返回等待过程中该线程是否被中断过。

  shouldParkAfterFailedAcquire()这个方法,我们也需要理解一下:该方法主要是用于检查状态,判读自己是否可以进入等待队列。

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
  

if (ws > 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;
}

  从方法可以看到,当前节点的前一个节点的状态为通知状态时,返回true,即表示已经告诉了前节点,当释放之后叫醒当前节点,当ws大于0时,表示当前节点的线程被中断或者是等待超时,此时需要将该节点从队列中去除,即node.prev = pred = pred.prev和pred.next=node,表示将pred节点删除,替换成了pred的prev节点;else 则将pred节点设置成为通知唤醒状态。让前驱在执行完成后通知当前节点。

parkAndCheckInterrupt()使线程真正的进入等待状态。

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

  park()会让当前线程进入waiting状态。在此状态下,有两种途径可以唤醒该线程:1)被unpark();2)被interrupt(),。需要注意的是,Thread.interrupted()会清除当前线程的中断标记位。

看了shouldParkAfterFailedAcquire()和parkAndCheckInterrupt(),现在让我们再回到acquireQueued(),总结下该函数的具体流程:

结点进入队尾后,检查状态,找到安全休息点;调用park()进入waiting状态,等待unpark()或interrupt()唤醒自己;被唤醒后,看自己是不是有资格能拿到号。如果拿到,head指向当前结点,并返回从入队到拿到号的整个过程中是否被中断过;如果没拿到,继续流程1。

release:
接下来看当线程释放锁,即Lock调用unlock时,源码情况:
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的实现就相对简单了,首先同样是调用我们用户自己实现的tryRelease()方法,当tryReleace返回true时,判断头节点状态是否等于初始值,不等于初始值时调用unparkSuccessor(node)方法释放锁,否则表示资源已经呗释放,直接返回true。
unparkSuccessor(node):
 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)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}

这个方法是用于唤醒一个线程,如果这个线程存在的话,ws小于0,表示当前线程是可以呗唤醒的,处于等待状态的线程,此时修改当前线程的状态为原始状态,如果当前线程的下一个线程为空,或者是状态大于0,表示处于CANCELLED状态,此时循环链表,找到最后一个状态小于0的线程,将找到的线程赋值给s,最后如果成功找到s,则使用unpark唤醒s节点的线程,这样就实现了release,

总结:release()是独占模式下线程释放共享资源的顶层入口。它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。

(四) 重写tryAcquire(int) 和 tryRelease(int) 方法实现一个简易的可重入锁

这里直接使用java文档的示例代码:

 class Mutex implements Lock, java.io.Serializable {    // Our internal helper class
private static class Sync extends AbstractQueuedSynchronizer {
// Report whether in locked state
protected boolean isHeldExclusively() {
return getState() == 1;
} // Acquire the lock if state is zero
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
} // Release the lock by setting state to zero
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
} // Provide a Condition
Condition newCondition() { return new ConditionObject(); } // Deserialize properly
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}// The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();    public void lock()                { sync.acquire(1); }
public boolean tryLock() { return sync.tryAcquire(1); }
public void unlock() { sync.release(1); }
public Condition newCondition() { return sync.newCondition(); }
public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

由于官网的文档实现的是不可重入的锁,所以本人也自己实现了一个可重入锁,下面是代码:

package com.ucar.work;import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/**
* @since: 2018/9/27
* @version: 1.0
* Copyright: Copyright (c) 2018
*/
public class MyLock implements Lock, java.io.Serializable { private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
} @Override
public boolean tryAcquire(int acquires) {
int stat = getState();
Thread thread = Thread.currentThread();
//表示线程第一次进如,直接加锁
if (stat == 0) { compareAndSetState(0,stat+acquires);
setExclusiveOwnerThread(thread);
return true;
//当前线程等于getExclusiveOwnerThread()表示线程重入。返回true表示可以获取锁
} else if (stat != 0 && thread == getExclusiveOwnerThread()){
return true;
}
//否则返回false表示获取锁失败
return false;
} @Override
protected boolean tryRelease(int releases) {
int stat = getState();
//stat==0表示该锁状态为释放状态,不能去释放
if (stat == 0) {
setExclusiveOwnerThread(null);
return false;
} else {
//重入的情况下减去重入次数
setState(getState()-releases);
}
//返回可释放信号
return true;
} Condition newCondition() { return new ConditionObject(); } private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0);
}
} private final Sync sync = new Sync(); @Override
public void lock() { sync.acquire(1); }
@Override
public boolean tryLock() { return sync.tryAcquire(1); }
@Override
public void unlock() { sync.release(1); }
@Override
public Condition newCondition() { return sync.newCondition(); }
public boolean isLocked() { return sync.isHeldExclusively(); }
public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}

实现细节注释已经写清楚了,这里就不在叙述了。

至于共享模式的AQS,会在以后的学习中继续记录,不足之处,忘各位大佬多多指点。

原文  并发编程学习笔记(5)—-AbstractQueuedSynchronizer(AQS)原理及使用

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:8,985
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,501
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,345
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,128
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,763
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,839