Java并发编程——AQS(Abstract Queued Synchronizer)

AQS简介

AQS是AbstractQueuedSynchronizer的简称,即抽象队列同步器;是一个用来构建锁和同步器的框架。

AQS的数据结构

AQS内部使用了一个volatile的变量state来作为资源的标识,同时定义了几个获取和改版state的protected方法,子类可以覆盖这些方法来实现自己的逻辑:

getState();
setState();
compareAndSetState();

这三种操作均是原子操作,其中compareAndSetState的实现依赖于Unsafe的compareAndSwapInt()方法;

而AQS类本身实现的是一些排队和阻塞的机制,如具体线程等待队列的维护,内部使用了一个先进先出的双端队列,并使用两个指针head和tail用于标识队列的头部和尾部,**但是它不是直接储存线程,而是储存拥有线程的Node节点,**其数据结构如图:

在这里插入图片描述

资源共享模式

资源有两种共享模式,或者说两种同步方式:

  • 独占模式:资源是独占的,一次只能一个线程获取;
  • 共享模式:同时可以被多个线程获取,具体的资源个数可以通过参数指定;
static final class Node {
 // 标记⼀个结点(对应的线程)在共享模式下等待
 static final Node SHARED = new Node();
 // 标记⼀个结点(对应的线程)在独占模式下等待
 static final Node EXCLUSIVE = null; 
 // waitStatus的值,表示该结点(对应的线程)已被取消
 static final int CANCELLED = 1; 
 // waitStatus的值,表示后继结点(对应的线程)需要被唤醒
 static final int SIGNAL = -1;
 // waitStatus的值,表示该结点(对应的线程)在等待某⼀条件
 static final int CONDITION = -2;
 /*waitStatus的值,表示有资源可⽤,新head结点需要继续唤醒后继结点(共享模式下,多线程并
 static final int PROPAGATE = -3;
 // 等待状态,取值范围,-3,-2,-1,0,1
 volatile int waitStatus;
 volatile Node prev; // 前驱结点
 volatile Node next; // 后继结点
 volatile Thread thread; // 结点对应的线程
 Node nextWaiter; // 等待队列⾥下⼀个等待条件的结点
 // 判断共享模式的⽅法
 final boolean isShared() {
 return nextWaiter == SHARED;
 }
 Node(Thread thread, Node mode) { // Used by addWaiter
 this.nextWaiter = mode;
 this.thread = thread;
 }
 // 其它⽅法忽略,可以参考具体的源码 }
// AQS⾥⾯的addWaiter私有⽅法
private Node addWaiter(Node mode) {
 // 使⽤了Node的这个构造函数
 Node node = new Node(Thread.currentThread(), mode);
 // 其它代码省略 }

通过Node我们可以实现两个队列,一是通过prev和next实现线程同步队列、双向队列,而是nextWaiter实现Condition条件上的等待线程队列(单向队列)。

AQS的主要方法源码分析

AQS的设计是基于模板方法模板的,它有一些方法必须要子类去实现的,主要有:

  • isHeldExlusively():该线程是否正在独占资源,只有用到condition才需要实现;
  • tryAcquire(int):独占方式,尝试获取资源,成功返回true,失败返回false;
  • tryRelease(int):独占方式,尝试释放资源,成功返回true,失败返回false;
  • tryAcquireShared(int):共享方式,尝试获取资源,负数表示失败,0表示成功,但没有剩余可用资源,正数表示成功,且有可用资源;
  • tryReleaseShared(int):共享方式,尝试释放资源,如果释放后允许唤醒后续等待节点返回true,否则返回false。
获取资源
public final void acquire(int arg) {
	 if (!tryAcquire(arg) &&
	 acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
	 selfInterrupt();
}

arg是要获取资源的个数,在独占模式下始终为1;

  1. 首先调用tryAcquire(arg)尝试去获取资源;
  2. 如果获取资源失败,就通过addWaiter(Node.EXCLUSIVE)方法把这个线程插入到等待队列中,其中传入的参数代表插入的Node是独占的;
private Node addWaiter(Node mode) {
	 // ⽣成该线程对应的Node节点
	 Node node = new Node(Thread.currentThread(), mode);
	 // 将Node插⼊队列中
	 Node pred = tail;
	 if (pred != null) {
	 node.prev = pred;
	 // 使⽤CAS尝试,如果成功就返回
	 if (compareAndSetTail(pred, node)) {
		 pred.next = node;
		 return node;
		 }
	 }
	 // 如果等待队列为空或者上述CAS失败,再⾃旋CAS插⼊
	 enq(node);
	 return node;
}
// ⾃旋CAS插⼊等待队列
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;
			 }
		}
	 }
}
  1. 现在通过addWaiter方法,已经把一个Node放到等待队列尾部,而处于等待队列的结点是一个一个去获取资源的,acquireQueued的实现代码:
final boolean acquireQueued(final Node node, int arg) {
 	 boolean failed = true;
	 try {
		 boolean interrupted = false;
		 // ⾃旋
		 for (;;) {
			 final Node p = node.predecessor();
			 // 如果node的前驱结点p是head,表示node是第⼆个结点,就可以尝试去获取资源了
			 if (p == head && tryAcquire(arg)) {
				 // 拿到资源后,将head指向该结点。
				 // 所以head所指的结点,就是当前获取到资源的那个结点或null。
				 setHead(node); 
				 p.next = null; // help GC
				 failed = false;
				 return interrupted;
			 }
			 // 如果⾃⼰可以休息了,就进⼊waiting状态,直到被unpark()
			 if (shouldParkAfterFailedAcquire(p, node) &&
				 parkAndCheckInterrupt())
				 interrupted = true;
			}
	 	 } finally {
		 if (failed)
		 	cancelAcquire(node);
	}
}
  1. 结点进入等待队列后,是调用park使它进入阻塞状态的,只有头结点的线程是处于活跃状态的;

在这里插入图片描述

释放资源
public final boolean release(int arg) {
	 if (tryRelease(arg)) {
	 	Node h = head;
	 if (h != null && h.waitStatus != 0)
		 unparkSuccessor(h);
		 return true;
	 }
 	return false; 
 }
 
private void unparkSuccessor(Node node) {
	 // 如果状态是负数,尝试把它设置为0
	 int ws = node.waitStatus;
	 if (ws < 0)
	 	compareAndSetWaitStatus(node, ws, 0);
	 	// 得到头结点的后继结点head.next
	 Node s = node.next;
		 // 如果这个后继结点为空或者状态⼤于0
		 // 通过前⾯的定义我们知道,⼤于0只有⼀种可能,就是这个结点已被取消
	 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值