AbstractQueuedSynchronizer分析

一、ReentrantLock原理分析

ReentrantLock中有个成员变量Sync,提供同步机制。Sync继承了AbstractQueuedSynchronizer。Sync有两种实现,公平和非公平。可以在构造ReentrantLock的时候传递一个布尔值来决定实现公平或非公平。默认为非公平。以下都是基于非公平的实现。

AbstractQueuedSynchronizer中包含了锁的状态state(volatile修饰的),提供一个阻塞队列(FIFO、双向链表),保存了获得锁的线程。在AQS中有很多变量用volatile修饰。

lock()方法

在这里插入图片描述
当线程进行加锁的时候,先通过CAS比较state的值是否是0,是就修改为1,表示抢占到锁,然后调用setExclusiveOwnerThread()方法将当前线程保存起来,说明当前线程独占了这个锁。
CAS操作保证只有一个线程能够对state修改成功

非公平体现在当有新的线程调用lock()时,刚好state为0,会去跟阻塞队列中的线程竞争,新的线程CAS成功抢占到锁,而阻塞队列中的线程还得继续等待。新的线程是没有进阻塞队列的。性能会更高,省去了阻塞跟唤醒操作,减少CPU开销。最坏的情况下,阻塞队列中的线程会一直拿不到锁。
公平的实现则是,线程在加锁之前会先去看阻塞队列是否有线程,有则加入到阻塞队列中去,而不是去抢占锁。这样先来的线程就会先获得锁。

private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
	exclusiveOwnerThread = thread;
}

CAS失败的则调用acquire()方法,参数传了1。
在这里插入图片描述
1、tryAcquire()方法,尝试着继续抢占锁。

final boolean nonfairTryAcquire(int acquires) {
   final Thread current = Thread.currentThread();
    int c = getState();
    //无锁状态,抢占锁
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //锁的持有者等于当前线程,即重入,state+1
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

2、addWaiter()方法,将线程加入到阻塞队列中。

private Node addWaiter(Node mode) {
	//new一个Node节点,mode是Node.EXCLUSIVE, 表示锁是独占的
    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) {//尾插法,队列已经初始化了,跟enq()方法类似
        node.prev = pred;
        //尝试一次就将节点插入到队列中,失败了,再调用enq()方法去重复的尝试插入队列
        if (compareAndSetTail(pred, node)) { 
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

在最开始的时候队列是没有初始化的,head、tail节点都是null,if判断不成立,调用enq(node)方法将节点加入到队列中。

private Node enq(final Node node) {
   for (;;) { //自旋,直到插入队列成功才结束
        Node t = tail;
        //队列还未初始化,第一次循环
        if (t == null) { // 初始化节点,head、tail指向一个空的Node
            if (compareAndSetHead(new Node()))//将head节点指向空的Node节点
                tail = head;//将tail节点也指向空的Node节点
        } else {  //队列已经初始化,若head、tail节点都指向一个空的Node节点
        	//这里的t是空的节点
            node.prev = t;//将代表线程的节点的前节点指向tail节点,即空的节点 
            if (compareAndSetTail(t, node)) {//修改tail节点指向代表线程的节点
                t.next = node;//将空的节点的下个节点指向代表线程的节点
                return t;
            }
        }
    }
}

就是将节点插入尾部,跟tail节点指向的原来的尾部节点连接起来,再将tail节点指向新的节点,新的 节点成为了尾节点。返回插入节点的前节点(即原tail节点)。
3、acquireQueued()方法

node当前线程节点,arg为1

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

for循环自旋,拿到当前线程节点的前一个节点。第一个判断,如果前一个节点为头节点,且tryAcquire()抢占锁成功,则把当前线程节点设置为head节点,将线程节点设置为null(已经抢到锁有执行权了,就不需要保存线程了)。第二个判断,shouldParkAfterFailedAcquire()判断当前线程节点在抢占锁失败后是否应该被挂起(节点的状态默认为0,第一次调用这个方法会将节点状态修改成SIGNAL,返回false,后面自旋再执行到这个方法时就会返回true了),是则调用parkAndCheckInterrupt()将线程挂起。意味着当前线程已被阻塞在这个地方(不会再执行了)。

unlock()方法

unlock()方法调用的release()方法
在这里插入图片描述
线程执行完后需要去释放锁,并且唤醒阻塞队列中的线程。

唤醒阻塞队列中的线程都是由head节点来唤醒的,FIFO,所以release()方法拿的是head节点

1、tryRelease()方法

参数releases为1

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//不为0,则代表重入了
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

将state值减一,如果state为0,则释放锁,将持有锁的线程设置为null。如果不为0,则不会去唤醒阻塞队列中的线程。

if (h != null && h.waitStatus != 0) unparkSuccessor(h);

释放锁成功后,进行判断,头节点不为空且头节点的状态不为0(前面shouldParkAfterFailedAcquire()方法将前一个节点的waitStatus设置为SIGNAL了,-1),则调用unparkSuccessor()方法去唤醒阻塞队列中的线程。

2、unparkSuccessor()

唤醒head节点的下一个节点的线程。

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus; // -1
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0); //修改head节点的状态

 
    Node s = node.next; //拿到下个节点,即需要被唤醒的线程节点
    if (s == null || s.waitStatus > 0) { //节点为空或节点状态为CANCELLED,则去除无效节点
        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); //唤醒线程
}

线程被唤醒后会继续去执行上面lock()方法中的acquireQueued()方法(这个方法是会一直自旋的,被挂起阻塞了就暂停,等着被唤醒后继续循环,直到抢占到锁才会结束)。

无限套娃,不说了

中断处理

线程在抢占锁失败后被阻塞了,在别的线程中去中断这个线程,中断线程会唤醒线程。为了响应中断,在parkAndCheckInterrupt()中会返回当前线程的中断标记。将这个中断标记往上层返回,在acquire()方法中会调用selfInterrupt()方法,进行自我中断。在这个线程获得锁后可以根据这个中断标记去做相应的处理。

实际上,就是为了告诉这个线程在阻塞期间被中断过,等到获得锁有执行权的时候线程再自行对这个中断信号进行处理。可以结束当前线程,也可以啥都不做。取决于线程是怎么处理的。

二、Condition分析

Condition是搭配锁来使用的,提供了await()和signal()方法

AbstractQueuedSynchronizer中有一个类ConditionObject,继承了Condition。

相对于AQS队列中的head、tail节点,condition队列中有firstWaiter、lastWaiter节点。AQS队列是同步队列,双向链表;condition队列是等待队列,单向链表。

await()方法

前提是调用这个方法的线程已经获得了锁。

当满足某种条件的时候可以调用await()方法让当前线程阻塞。

在这里插入图片描述

1、addConditionWaiter()

将线程添加到condition等待队列中。

private Node addConditionWaiter() {
    Node t = lastWaiter; 
    //如果尾节点是无效的,则清除掉
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

将当前线程包装成一个节点,节点状态为 CONDITION。同样是尾插法,如果condition队列中没有线程节点,则将firstWaiter和lastWaiter都指向加入的节点;如果已经存在线程节点了,将加入的节点设置为lastWaiter指向的线程节点的next,然后再将lastWaiter指向加入的节点。可以看出这里只是从前指向后,是单向的。

2、fullyRelease()

加入到condition等待队列中后,需要释放锁。

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

int savedState = getState(); // 获得state的值
if (release(savedState)) //将state的值传过去

这里release方法的参数是state的值而不是1,是为了完全的释放锁,因为当前线程是有可能多次重入获得了锁。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

跟上面unlock()中的方法是一样的操作。释放锁,并唤醒AQS队列中head的下一个节点。

3、isOnSyncQueue()

判断加入condition队列的节点的状态是CONDITION,返回false。然后调park()方法阻塞当前线程。线程就被阻塞在这个地方了,当线程被唤醒的时候,继续执行。继续while循环,调用isOnSyncQueue(),节点状态已经被改为0,且加入了AQS队列,返回ture,结束while循环。

4、checkInterruptWhileWaiting()

线程被唤醒之后,判断是否有被中断,有则break,没有则继续while循环。

5、acquireQueued()

上面已经分析过了,即自旋抢占锁。

await()方法最后面的两个判断,一个是为了清除无效节点,一个跟中断处理有关。

signal()方法

在这里插入图片描述
获取到等待队列中的firstWaiter,不为空则调用doSignal()方法,参数为firstWaiter。

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null) 
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

doSignal()中是do…while循环,先把first的下个节点赋给firstWaiter,判断是否为null,是则把lastWaiter设为null(说明等待队列中没有其他节点了)。first.nextWaiter = null,将first节点在等待队列中断开,方便GC。然后进入while判断,如果transferForSignal()方法返回false,则表明当前的first节点有异常,然后将等待队列中的下个节点赋给first,再进行循环(如果下个节点为null,则结束循环)。

接下来看看transferForSignal()的实现。

final boolean transferForSignal(Node node) {

    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
        
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

transferForSignal()方法首先进行一个CAS操作,将节点状态从CONDITION改为0,如果失败则返回false,回到doSignal()方法继续执行(修改失败代表当前first节点是CANCELLED状态)。修改成功则调用enq()方法将节点加入到AQS队列中(enq()方法上面已经讲过,不再赘述)。enq()方法返回原tail节点 p (原tail节点有可能是head节点指向的节点,即AQS队列中没有阻塞的线程,或者是其他线程节点)。获取到p节点的状态进行判断,如果大于0(即CANNELLED状态)或者CAS修改为SIGNAL状态失败,则调用unpark()方法唤醒上面加入AQS队列的线程(在这个地方唤醒,是为了提升性能。无效节点会遍历队列进行清除,AQS队列线程会被唤醒)。

v1.1 AQS在ReentrantLock中的实现
v1.2 Condiotion的实现
v1.3 待补充。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值