AQS之ReentrantLock(入门级理解)

   日期:2020-05-20     浏览:101    评论:0    
核心提示:ReentrantLock:1.可重入2.公平锁或者非公平锁3.手动加锁,手动释放AQS:ReentrantLock内部维护了一个AQS队列,AQS的本质是一个双向链表。AQS中存放着排队等待锁的线程对象(Node)ReentrantLock公平锁的加锁过程public final void acquire(int arg) { //tryAcquire(arg):尝试当前线程加锁 //acquireQueued(addWaiter(Node.EXCLUSIVE), arg)):数据结构与算法

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)
    
        return true;
    if (ws > 0) {
        
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {//线程节点第一次进入的时候,waitStatus=0 或者等于 -3
        
         
         //前一个线程的节点状态由当前线程设置
         //原因: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
    
    int ws = node.waitStatus;
    if (ws < 0)//重置head节点的waitStatus = 0
        node.compareAndSetWaitStatus(ws, 0);

    
    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中排队

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服