AQS是什么?
在 Lock 中,用到了一个同步队列 AQS,全称 AbstractQueuedSynchronizer,它是一个同步工具也是 Lock 用来实现线程同步的核心组件。
java.util.concurrent中大部分的工具都是通过AQS实现的。
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
如果看不明白不要怕继续往下看,看完回头再来看这个图。
接下来主要是通过ReentrantLock实现的Lock接口中的lock()锁来讲解上锁的过程。
AQS(AbstractQueuedSynchronizer)的类图如下:
类图
代码实例
代码示例:
public class AtomicDemo {
private static int count=0;
static Lock lock=new ReentrantLock();
public static void inc(){
//本文主要通过lock()方式来探究源码的实现过程
lock.lock();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
System.out.println(count);
lock.unlock();
}
public static void main(String[] args) throws
InterruptedException {
for(int i=0;i<1000;i++){
new Thread(()->{AtomicDemo.inc();}).start();;
}
Thread.sleep(3000); System.out.println("result:"+count);
}
}
ReentrantLock中的lock()方法
public void lock() {
sync.lock();
}
sync.lock()方法
abstract void lock();
sync.lock()有两个实现类,其中是一个是公平策略实现类,一个是非公平策略实现类。(公平策略与非公平策略后边会对比讲解)
ReentrantLock默认的是非公平方法实现类。可以根据ReentrantLock的构造方法来确定。
++++++++++++分割线(这是一段插曲)+++++++++++++++++
ReentrantLock构造方法代码
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
++++++++++++分割线结束+++++++++++++++++++++
回到sync.lock()方法的实现方法
lock()方法先通过CAS尝试将状态从0修改为1。若直接修改成功,前提条件自然是锁的状态为0,则直接将线程的OWNER修改为当前线程,这是一种理想情况,如果并发粒度设置适当也是一种乐观情况。
接下来分析这段代码了首先要进入的是compareAndSetState()方法主要的作用就是确定当前资源是否被占用,资源没有被占用就是理想状态。
代码的实现如下:
acquire(1)方法
如果非理想状态就会进入到acquire(1)方法中
public final void acquire(int arg) {
//对tryAcquire()的调用判定中是通过if(!tryAcquire())作为第1个条件的,如果返回true,则判定就不会成立了,自然后面的acquireQueued动作就不会再执行了,如果发生这样的情况是最理想的
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
接下来咱们去tryAcquire()中看看
ReentrantLock类中有多种tryAcquire()实现方案首先咱们进入公平策略的实现方法中进行分析
//公平策略
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//首先获取这个锁的状态
if (c == 0) {//如果状态为0
if (!hasQueuedPredecessors() &&//判断线程是否需要排队
compareAndSetState(0, acquires)) {//尝试设置状态为传入的参数(这里就是1)
setExclusiveOwnerThread(current);//若设置成功就代表自己获取到了锁
return true;
}
}
//线程重入的体现,至于重入是什么可以先自行查找,此次不做说明
else if (current == getExclusiveOwnerThread())//如果状态不是0,则判定当前线程是否为排它锁的Owner
{
int nextc = c + acquires;//如果是Owner则尝试将状态增加acquires(也就是增加1)
if (nextc < 0)//如果这个状态值越界,则会抛出异常提示
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;//若没有越界,将状态设置进去后返回true(实现了类似于偏向的功能,可重入,但是无需进一步征用)
}
return false;//如果状态不是0,且自身不是owner,则返回false
}
addWaiter()方法
private Node addWaiter(Node mode) {
//这里创建了一个Node的对象,将当前线程和传入的Node.EXCLUSIVE传入,也就是说Node节点理论上包含了这两项信息。代码中的tail是AQS的一个属性,刚开始的时候肯定是为null,也就是不会进入第一层if判定的区域,而直接会进入enq(node)的代码
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;
}
进入enq()方法
//看到了tail就应该猜到了AQS是链表吧,没错,而且它还应该有一个head引用来指向链表的头节点,AQS在初始化的时候head、tail都是null,在运行时来回移动。此时,我们最少至少知道AQS是一个基于状态(state)的链表管理方式。
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;
}
}
}
}
接下来返回到acquireQueued()方法中
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//这个方法返回node节点的前一个节点,也就是说只有当前一个节点是head的时候,进一步尝试通过tryAcquire(arg)来征用才有机会成功
if (p == head && tryAcquire(arg)) {//p为node.predcessor()得到,这个方法返回node节点的前一个节点,也就是说只有当前一个节点是head的时候,进一步尝试通过tryAcquire(arg)来征用才有机会成功
setHead(node);//调用setHead(Node)的操作,这个操作内部会将传入的node节点作为AQS的head所指向的节点。线程属性设置为空(因为现在已经获取到锁,不再需要记录下这个节点所对应的线程了),再将这个节点的perv引用赋值为null。
p.next = null; // help GC//进一步将的前一个节点的next引用赋值为null。在进行了这样的修改后,队列的结构就变成了以下这种情况了,通过这样的方式,就可以让执行完的节点释放掉内存区域,而不是无限制增长队列,也就真正形成FIFO了
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) //这个方法内部会判定前一个节点的状态是否为:“Node.SIGNAL”,若是则返回true,若不是都会返回false,不过会再做一些操作:判定节点的状态是否大于0,若大于0则认为被“CANCELLED”掉了(我们没有说明几个状态的值,不过大于0的只可能被CANCELLED的状态),因此会从前一个节点开始逐步循环找到一个没有被“CANCELLED”节点,然后与这个节点的next、prev的引用相互指向;如果前一个节点的状态不是大于0的,则通过CAS尝试将状态修改为“Node.SIGNAL”,自然的如果下一轮循环的时候会返回值应该会返回true。&&
parkAndCheckInterrupt())//通过LockSupport.park(this)将当前线程挂起到WATING状态,它需要等待一个中断、unpark方法来唤醒它,通过这样一种FIFO的机制的等待,来实现了Lock的操作。
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
公平策略与非公平策略对比
公平策略与非公平策略的唯一区别就是判断是否需要排队。