AbstractQueuedSynchronizer源码深度解析

#总体介绍
基于队列的抽象同步器,它是jdk中所有显示的线程同步工具的基础,像ReentrantLock/DelayQueue/CountdownLatch等等,都是借助AQS实现的。Java中已经有了synchronized关键字,那么为什么还需要来这么一出呢?因为AQS能实现更多维度,更多场景的锁机制,例如共享锁(读锁)/基于条件的线程阻塞/可以实现公平和非公平的锁策略/可以实现锁等待的中断,而synchronized关键字由JVM实现,在代码使用层面来说,如果仅仅是使用独占锁,那synchronized关键字比其它的锁实现用起来方便。
下面步入正题,来看看AQS都提供了哪些能力。

#1、主体结构

先来看看内部的主体结构:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
		

    protected AbstractQueuedSynchronizer() { }
	
	//内部类Node,代表每一个获取或者释放锁的线程
	static final class Node{}
	
	//head 和 tail构成了同步队列链表的头节点和尾节点。
	private transient volatile Node head;
	
	private transient volatile Node tail;
	
	//同步状态值,所有的同步行为都是通过state这个共享资源来实现的。
	private volatile int state;
	
	//条件对象,用于同步在当前锁对象上的线程
	public class ConditionObject implements Condition, java.io.Serializable{}
	
	/******
	一系列的内部方法:
	包括尝试获取锁权力
	尝试获取锁失败后构造节点放入等待队列
	各种入队出队的操作
	尝试释放锁等
	********/
	
	
	//一系列的Unsafe操作
	private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    private static final long waitStatusOffset;
    private static final long nextOffset;
}

所以总结起来AQS就是通过一系列的Unsafe方法去操作一个链表队列,而链表队列中每个节点需要操作的共享资源就是一个int state字段,如果要用到条件等待,则需要了解ConditionObject。

#2、节点类的构造
再来看看Node节点的内部结构:

static final class Node {
	//常量,见nextWaiter属性
	static final Node SHARED = new Node();
	static final Node EXCLUSIVE = null;

    //waitStatus 常量值。表示等待获取锁的线程已经被取消(线程中断/等待超时)
	static final int CANCELLED =  1;
    //waitStatus 常量值。表示后继线程需要unpark  
	static final int SIGNAL    = -1;
	//waitStatus 常量值。表示线程正在Condition队列中
	static final int CONDITION = -2;
	//waitStatus 常量值。表示线程的acquireShared行为需要无条件的向队列的下一个节点传递。用在共享锁的场景。
	static final int PROPAGATE = -3;
	//注意,waitStatus除了以上常量值以为,由于是int类型,则默认是0
	volatile int waitStatus;

    //前继节点
	volatile Node prev;

	//后继节点
	volatile Node next;

    //节点所代表的线程
	volatile Thread thread;

	//如果节点再Condition等待队列中,则该字段指向下一个节点,如果节点在同步队列中,则为一个标志位,其值为 SHARED或者null
	Node nextWaiter;

	final boolean isShared() {
		return nextWaiter == SHARED;
	}

	final Node predecessor() throws NullPointerException {
		Node p = prev;
		if (p == null)
			throw new NullPointerException();
		else
			return p;
	}

	Node() {    // Used to establish initial head or SHARED marker
	}

	Node(Thread thread, Node mode) {     // Used by addWaiter
		this.nextWaiter = mode;
		this.thread = thread;
	}

	Node(Thread thread, int waitStatus) { // Used by Condition
		this.waitStatus = waitStatus;
		this.thread = thread;
	}
}

所以在同步队列中Node应该长下面这个样子:
同步队列
在Condition等待队列中应该长如下这个样子:
等待队列
Node节点一共有3个构造方法他们分别在同步队列初始化时/加入同步队列/加入Condition队列时使用,结合构造方法以及AQS和Condition类,我们看下最终队列会长什么样子。
首先看一下无参的构造方法在什么地方使用:

private Node addWaiter(Node mode) {
	Node node = new Node(Thread.currentThread(), mode);  //用当前线程和mode类型构造节点,此时waitStatus默认为0
	// 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;
}

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

addWaiter方法是将线程放入队列的方法,开始构造new Node(Thread.currentThread(), mode)这样的节点,然后尝试入队列,如果入队列失败则进入enq方法,在enq中,如果队列为空(当t==null的时候,因为入队操作是从队尾进行的),此时构造了一个空的Node节点,然后将head和tail指向它。我们称这个节点为哨兵节点,为什么需要这样一个节点,后面会说明,哨兵节点入队列后我们通过当前线程构造的的节点new Node(Thread.currentThread(), mode)再入队列。由于构造方法没有传入waitStatus值,所以此时waitStatus默认为0。

所以综合AQS来看,最终的同步队列应该长如下这个样子:

同步队列中Node节点的存放情况

那么在Condition等待队列中呢? Condition队列中有firstWaiter和lastWaiter分别指向头节点和尾节点。
先看下加入Condition队列的代码:

private Node addConditionWaiter() {
	Node t = lastWaiter;
	// If lastWaiter is cancelled, clean out.
	if (t != null && t.waitStatus != Node.CONDITION) {
		unlinkCancelledWaiters();
		t = lastWaiter;
	}
	//构造waitStatus等于CONDITION的NODE节点
	Node node = new Node(Thread.currentThread(), Node.CONDITION);
	if (t == null) //如果队列为空,则新节点入队列,头节点指向新节点
		firstWaiter = node;
	else
		t.nextWaiter = node; //如果队列不为空,则新节点加入到队列末尾
	lastWaiter = node;  //尾节点指向新节点
	return node;
}

所以,最终Condition队列中的节点如下:
Condition队列中的节点
Condition队列中的节点是单向的,并且没有哨兵节点,在Condition队列中,nextWaiter指向下一个节点,而不是像在同步队列中那样指向模式(SHARED, null),其中各个节点的waitStatus等于CONDITION(-2)。

3、获取锁

##3.1 首先看写锁lock的实现

public void lock() {
	sync.acquire(1);  //调用同步器的acquire方法 
}

这里同步器由ReentrantReadWriteLock来继承AQS后具体实现,不同的锁对AQS的实现是不一样的。

public final void acquire(int arg) {
	if (!tryAcquire(arg) &&  //尝试获取锁失败
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) //并且节点入队列成功
		selfInterrupt();   //线程中断
}

整个acquire方法其实就是3部曲,①尝试获取锁 ②如果获取锁没有成功,则构造节点加入等待队列中 ③如果节点入队列成功,则线程自我中断让出资源。当线程进入同步队列中后就实现了获取锁过程中的线程阻塞功能了,因为这个时候线程是自我中断状态。

protected boolean tryAcquire(int arg) {
	throw new UnsupportedOperationException();
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值