我是在学习ReentrantLock的时候看到了AQS这个东西的,于是就去看了他的源码
Node结点介绍
在AQS里面有一个内部类为Node
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
SHARED为结点的模式意思为共享模式,EXCLUSIVE表示为结点的模式为独占模式。
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
上述似乎是Node对象的状态描述的几个参数。
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
waitStatus表示的是等待状态也是模式的一种,后面三个prev、next、thread则是Node对象存储的主要内容,应该说的是在AQS中Node和Node之间是以双向链表的形式存在的。如下图所示。
prev指向前置节点,next指向后置节点。这里的前置和后置是相对于本身节点而言。 在AQS中head指向头节点,tail指向尾巴节点。thread存储的是当前节点的线程任务。
Node() {}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
Node具有的三种构造器
1.无参构造器,没有做任何操作
2.有参构造器带thread和node对象的将他们赋值给nextWaiter和thread
3.有参构造器带thread和waitStatus等待状态的将他们赋值给waitStatus和thread
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node的内部方法predecessor,将prev属性的值(指向的是该节点的前置节点)赋值给一个实例的Node对象p,如果p为空则没有前置节点,本节点即为头结点。否则返回的是当前节点的前置结点。总结:如果本节点是头结点,则抛异常,反之则将前置节点返回。
final boolean isShared() {
return nextWaiter == SHARED;
}
Node的内部方法isShared,该方法将字段SHARED和nextWaiter两个Node对象做比较,是否一直的boolean判断返回。
接下来看AQS自己的内容。
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
由前面可以知道head和tail分别是指向AQS链表的头和尾结点。state为状态,暂不知道是什么状态,有两个方法分别是简单的get和set方法,简单理解就是取值和赋值的两个方法。
AQS骨架介绍
AQS大概分为以下几个部分:
1.获取独占锁
2.获取共享锁
3.获取锁失败后将线程丢进等待队列
4.释放锁:独占锁和共享锁
入队操作,将线程丢进等待队列
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; } } } }private final boolean compareAndSetHead(Node update) { return unsafe.compareAndSwapObject(this, headOffset, null, update); }private final boolean compareAndSetTail(Node expect, Node update) { return unsafe.compareAndSwapObject(this, tailOffset, expect, update); }
AQS的enq方法(入队操作),入参为一个Node对象,使用for(;;)类似于while(true),只要没有return,则重复执行,将tail对象的值赋给新的Node实例t,健壮性判断 如果t为null则表示tail节点为null,那么就是之前并不存在尾节点,此时初始化使用一个CAS原子性操作,比较this对象的headoffset偏移量处的值是否为 null,如果值为 null,则将 headOffset处的值替换为新的节点 update当前线程返回 true。如果值不为 null,则说明其他线程已经在执行该操作,当前线程返回false就行。当为true时,将tail尾结点的指向也设置为head。此时初始化完成,效果如下图所示。
多提一嘴:将tail=head;是将head的引用赋给tail,所以他们指向同一个东西,那就是在if判断条件中CAS中new出来的Node结点对象。
如果t不为null,说明队列不为空,此时只需要将当前结点插入队列的最后。代码实现是将t(尾巴结点)赋值给当前结点的前置节点即node.prev。在if判断中又是一个CAS原子性操作,将尾巴结点设置为当前待插入的结点。只有一个线程可以设置成功,成功之后返回true,进入if内部,同时将之前的尾结点的下一个结点设置为当前结点。最后将尾结点返回。设置尾结点的流程可以看下图。
总结一下:这个enq的入队操作就是先看是否有尾结点,没有则说明需要初始化,初始化之后因为for(;;),重新判断此时将当前结点插入到队末,返回当前队列的尾巴结点。
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; }
这个是addWaiter,顾名思义就是将线程添加到等待队列中,创建一个node对象,将当前工作线程和mode模式放入,接来下获取队列的尾结点tail并赋值给pred,如果pred不为空,则表示队列中已经有节点存在,在这种情况下将新结点的prev指向pred,然后通过CAS将新结点设置为队列的尾结点,将pred的next指针指向新节点,最后返回新节点。(此处的处理其实同enq的处理相同,但是目的是不同的,在addWaiter中的空判断是查看是否可以使用快速路径添加,将结点添加到队列当中,而在enq中的判断则是验证队列是否初始化,是一种健壮性的判断),至此为AQS同步队列的入队操作。
获取独占式锁
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }//这里只展示两行代码就可以理解了。 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode);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); } }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; }
这个过程为获取独占式锁的过程,主要为以下几个方法构成
1.tryAcquire
2.addWaiter
3.acquireQueued
4.selfInterrupt
tryAcquire方法需要子类对象重写,否则直接抛出异常UnsupportedOperationException,主要作用为获取同步状态。
addWaiter会包装一个新的Node结点对象,其中将模式通过传参注入,由之前可见EXCLUSIVE = null,此时状态是独占锁
acquireQueued首先获取了当前节点的前置节点,后续做了两个if的判断,首先第一个,如果前置节点是头结点且当前节点获取到了同步状态,那么直接将当前节点设置为头结点,并且结点的引用设置为null方便GC的回收,(可能有人有疑问了,这个线程执行了吗,怎么刚设置为头结点就全部设置为null了,当然是执行了具体的线程执行是在tryAcquire(arg)
方法中进行的,所以此时的node结点已经完成了任务,他的作用是为了存储线程对象的引用,既然已经执行完,那么就不需要继续存储了,设置为null,等新的头结点诞生之后,新的头结点的node.prev是指向旧的头结点,也会被取消,此时旧的头结点就是等待被GC回收),会不会讲的有点啰嗦。第二个if判断中,将前置节点和当前节点统统传入shouldParkAfterFailedAcquire方法中,获取前置节点的等待状态,如果等待状态为SIGNAL,文章最开始有提到,这个是-1,这是一个释放信号