AQS之ReentrantLock(入门级理解)

ReentrantLock:
1.可重入
2.公平锁或者非公平锁
3.手动加锁,手动释放

AQS:ReentrantLock内部维护了一个AQS队列,AQS的本质是一个双向链表。AQS中存放着排队等待锁的线程对象(Node)

ReentrantLock公平锁的加锁过程

public final void acquire(int arg) {
    //tryAcquire(arg):尝试当前线程加锁
    //acquireQueued(addWaiter(Node.EXCLUSIVE), arg)):将当前线程加入到AQS队列
    //selfInterrupt():重置线程interrupted状态
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();//线程被唤醒后,还原interrupt.因为Thread.interrupted()可能会重置interrupt
}

tryAcquire(arg) :尝试获取锁,如果能加锁成功,返回true,加锁失败返回false

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    //获取当前锁的状态(state)
    /*0:没有线程获取锁或者获取到锁的对象已经释放释放锁
    /*1:表示当前锁已经被线程持有了
    /*大于1:表示当前锁被线程重入了
    int c = getState();
    if (c == 0) {//表示当前锁没有线程得到或者得到当前锁的线程已经释放锁
        //hasQueuedPredecessors:查看当前AQS队列是否有线程在排队
        //第一个线程加锁时,hasQueuedPredecessors()返回false,继续执行CAS给当前线程加锁
        //CAS成功之后执行setExclusiveOwnerThread(),将当前线程设置为持有锁的线程,当前线程加锁成功
       if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //c!=0:当前lock已经被线程持有,如果当前线程是持有当前锁的线程,state+1(表示当前锁的重入次数+1),当前线程加锁成功
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //c!=0 && exclusiveOwnerThread != currentThread:lock已经被其他线程持有,当前线程加锁失败
    return false;
}

hasQueuedPredecessors():查看AQS队列是否有线程在排队等待唤醒

public final boolean hasQueuedPredecessors() {
    Node h, s;
    //head:AQS队列的头节点,如果AQS队列没有被初始化的时候,head==null
    //     如果AQS队列已经被初始化了,head指向AQS的第一个Node节点
    if ((h = head) != null) {//第一个线程访问的时候,AQS队列没有初始化,head==null
        if ((s = h.next) == null || s.waitStatus > 0) {
            s = null; // traverse in case of concurrent cancellation
            for (Node p = tail; p != h && p != null; p = p.prev) {
                if (p.waitStatus <= 0)
                    s = p;
            }
        }
        if (s != null && s.thread != Thread.currentThread())
            return true;
    }
    return false;
}

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)):将当前线程加入到AQS队列

Node节点的构造
//Node的主要参数(从中可以看出AQS是一个双向链表)
   Node{
       Node pre;//指向当前节点的前一个节点
       Node next;//当前节点的下一个节点
       Thread thread;//当前节点绑定的线程对象
       volitale int waitStatus;//当前node节点的状态(0:初始状态 -1:可以被唤醒 1:取消 -2:condition -3:progate)
}

1.addWaiter(Node.EXCLUSIVE):初始化AQS队列或者直接将当下线程的node节点加到队尾tail(AQS队列的队尾)
private Node addWaiter(Node mode) {
Node node = new Node(mode);//初始化一个node节点 node{pre=null,next=null,waitStatus=0,thread:this(当前线程)}

//如果AQS队列已经被初始化过了,就直接将当前线程节点设置为tail
//如果AQS队列还没有被初始化,首先执行initializeSyncQueue()初始化AQS队列,然后再将当前node设置为tail
for (;;) {//自旋,相当于while(true)
    //tail:AQS队列的队尾。 AQS队列没有初始化的时候,tail=null
    Node oldTail = tail;
    if (oldTail != null) {//将当前的node节点设置为新的tail节点
        node.setPrevRelaxed(oldTail);//将node节点的上一个节点设置为之前老的tail
        if (compareAndSetTail(oldTail, node)) {//CAS将新的node设置为新的tail
            oldTail.next = node;
            return node;
        }
    } else {
        //初始化AQS队列
        initializeSyncQueue();
    }
}

}

initializeSyncQueue():初始化AQS队列(AQS的精髓:haad节点的thread永远为null)

private final void initializeSyncQueue() {
    Node h;
    //new Node()构造了一个thread=null的空节点,此时head和tail都指向同一个节点
    if (HEAD.compareAndSet(this, null, (h = new Node())))
        tail = h;
}

2:acquireQueued(Node node, arg)):将node加入到AQS队列
//node:当前线程的节点 ,arg = 1(传进来的就是1)

final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;//表示线程的打断状态
    try {
        //执行情况:1,线程加锁失败,加入到AQS,2.释放锁时,线程被唤醒,继续执行
        for (;;) {
            final Node p = node.predecessor();//当前节点的前一个节点
            //前一个节点是头节点,说明了当前的线程节点是第二个,这个时候调用tryAcquire(arg)再次尝试加锁
            //原因:可能当前线程在加入到AQS队列的时候,之前持有锁的线程释放了锁,这个时候当前线程就有机会直接获取锁
            if (p == head && tryAcquire(arg)) {
                //当前线程节点获取尝试获取到了锁,会将当前节点设置为新的head节点,
                //并且将节点的thread设置为null,pre也设置为null,断开指向前一个节点的引用
                setHead(node);
                //将之前head节点指向当前节点的引用设置为null,之前的head对象就没有指向其他对象,也没有其他对象指向它
                //那么根据GC Roots可达性分析,这样的对象会被gc
                p.next = null; // help GC
                return interrupted;
            }
            //AQS队列中已经有其他线程在排队了,那么当前线程节点park,而不用尝试tryAcquire(原因:前一个线程都还在排队,当前线程就没有必要去尝试获取锁),
            //park的过程分为两步
            //  1.将当前节点的前一个节点的waitStatus设置为-1
            //  2.将当前线程park
            if (shouldParkAfterFailedAcquire(p, node))//将当前线程的前一个节点的waitStatus设置为-1
                //|= 相当于 != 
                interrupted |= parkAndCheckInterrupt();//park当前线程
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        if (interrupted)
            selfInterrupt();
        throw t;
    }
}

shouldParkAfterFailedAcquire(p, node):设置当前节点的前一个节点的waitStatus
p

rivate static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;//获取前一个节点的waitStatus
    //ws = -1,表示当前线程是可以被唤醒的
    if (ws == Node.SIGNAL)//在被其他线程设置为-1之后,该方法直接返回true
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {//线程节点第一次进入的时候,waitStatus=0 或者等于 -3
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
         
         //前一个线程的节点状态由当前线程设置
         //原因:1.当前线程不能确定自己的线程状态,只要其他线程才能确定当前线程的状态
         //     2.当前线程设置自己状态的过程可能会异常
        pred.compareAndSetWaitStatus(ws, Node.SIGNAL);//将当前线程节点的前一个的waitStatus设置为-1
    }
    return false;
}

parkAndCheckInterrupt():将当前线程park,加锁过程完成

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);//park当前线程
    //当前线程被unpark之后,继续从这里执行
    //Thread.interrupted():
        //如果当前线程被其他线程调用了interrupt方法,Thread.interrupted()返回true,同时将interrupt重置
    return Thread.interrupted();
}

ReentrantLock的解锁过程

public void unlock() {
    sync.release(1);
}

tryRelease(arg)://尝试释放锁

protected final boolean tryRelease(int releases) {//releases = 1
    //如果state = 1,说明当前线程没有重入, 大于1说明线程重入
    int c = getState() - releases;//释放lock之后,当前锁的状态 ,如果= 0 则当前线程不再持有锁,大于1说明当前线程依然持有锁
    if (Thread.currentThread() != getExclusiveOwnerThread())//正常来说说不可能的
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//当前线程不再持有锁,将exclusiveOwnerThread设置为null,释放锁成功
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);//设置锁状态
    return free;
}

release(int arg):释放锁

public final boolean release(int arg) {
    //尝试释放锁,释放锁成功,tryRelease会返回true。这里有两种情况
    //1.当前线程没有重入
    //2.线程重入了,但是全部释放了。所以锁重入了多少次就要释放多少次。否则该线程就会一致持有锁
    if (tryRelease(arg)) {
        //唤醒下一个线程
        Node h = head;
        
        //对h != null && h.waitStatus != 0的情况分析
        // 1. h == null,表明了AQS队列没有初始化过,对于AQS没有初始化的情况有两种
        //     a.只要一个线程获取锁
        //     b.多个线程在获取锁,但是它们直接是交替执行的,不需要进入AQS中排队等待。也就是说这些线程加锁时第一次tryAcquire时都会加锁成功
        // 2. h != null && h.waitStatus != 0,表示了AQS已经被初始化过了,并且队列中还有等待唤醒的线程
        //原因:h != null说明head是存在的
        // h.waitStatus != 0 (也就是 = -1),因为节点的waitStatus是由下一个节点设置的,
        //所以waitStatus不等于默认值0就说明了下一个节点是存在的
        if (h != null && h.waitStatus != 0)
            //唤醒下一个可被唤醒的节点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor(h):唤醒头节点的下一个可被唤醒的节点(不一定是头节点的下一个)

private void unparkSuccessor(Node node) {//node = head
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)//重置head节点的waitStatus = 0
        node.compareAndSetWaitStatus(ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;//头节点的下一个节点
    //这一块代码几乎可以不需要考虑,因为线程被cancel只会出现在我们自己实现AQS出异常的时候调用cancelAcquire,
    //jdk实现的AQS不会出现
    if (s == null || s.waitStatus > 0) {//下一个节点不存在或者是被canceled状态,就从队尾开始找最靠近头节点的可被唤醒的节点
        s = null;
        for (Node p = tail; p != node && p != null; p = p.prev)
            if (p.waitStatus <= 0)
                s = p;
    }
    if (s != null)
        //唤醒线程
        LockSupport.unpark(s.thread);
}

unparkSuccessor(h)执行完成之后,park的地方继续执行,继续执行tryAcquire(),尝试加锁过程。加锁成功后将当前唤醒的节点中thread设置为null。lock解锁完成

head中的thread=null有两层含义:1.空节点 2.当前持有锁的线程

总结:当第一个线程来请求加锁的时候,可以直接拿到锁,而没有去初始化AQS队列。当第二个线程请求加锁时候,判断当前锁有没有被某一个线程持有,如果没有其他线程持有,就去查看AQS队列中有没有线程在等待唤醒,如果AQS没有被初始化或者没有线程在等待唤醒,那么第二个线程就可以获取锁。如果其他线程已经持有了锁,再看第二个线程是不是当前持有锁的线程,如果是的话,当前线程也可以加锁成功,同时lock的state+1,表示锁的重入次数+1。如果当前持有锁的线程和第二个线程不是同一个线程,那么第二个线程要加入的AQS队列中去。在加入到队列的过程中,如果第二个线程排在第二位(head.next指向的节点)会去再次尝试获取锁,如果不是的话就直接加入到AQS队列中。加入AQS时会将当前节点的前一个节点的waitStatus设置为-1,同时将当前节点设置为tail,等待被唤醒。AQS队列中的节点在得到锁之后,会将原来的head节点引用断开,将当前节点设置为新的head,同时将节点的thread设置为null.
当持有锁的对象释放锁时,会讲head节点的waitStatus设置为0,同时查看AQS是否存在,或者AQS中是否有等待的线程,如果有等待的线程那么就唤醒第一个可被唤醒的线程。

非公平锁与公平锁加锁过程的区别
公平锁:公平锁加锁时会检查AQS中是否有排队等待唤醒的线程,如果有的话,当前线程会加入到AQS中排队,而不是尝试获取锁

protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

非公平锁:加锁时不会检查AQS中是否有排队等待唤醒的队列,而是直接尝试获取锁,如果获取不到锁才会加入到AQS中排队

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    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;
}

公平锁与非公平锁的区别就是在没有线程持有锁的情况下,公平锁回去查看AQS中是否有线程在排队,如果有线程在排队,那么当前线程也加入排队。而非公平锁则是先进行一次尝试加锁,如果加锁失败了才会加入到AQS中排队

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值