AQS-公平锁锁逻辑与释放锁逻辑--个人笔记

公平锁逻辑与释放锁逻辑

锁逻辑

final void lock() {
    acquire(1);
}

调用lock方法时实际调用的是acquire()方法

public final void acquire(int arg) {
    //tryAcquire(arg)尝试去获取锁
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

tryAcquire()获取锁成功,则加锁

获取锁失败,则addWaiter()将所有竞争锁失败的线程入队,并且调用acquireQueued()方法

 protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();//拿到当前线程状态
        if (c == 0) {//如果未加锁则判断前面是否有其他线程,无则用cas方法加锁
            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;
        }
     //加锁失败返回false
        return false;
    }
}


private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        Node pred = tail;
    //如果尾节点不为null,执行快速入队
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
    //如果为null或者入队失败,走完整入队逻辑
        enq(node);
        return node;
    }

 private Node enq(final Node node) {
        for (;;) {
            //自旋入队,将所有节点入队
            Node t = tail;
            //如果尾节点为null,说明当前队列是空的,则需要帮持锁线程在队列中补充一个节点
            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方法
//acquireQueued方法做的事情就是将节点挂起 或者 节点被唤醒之后的逻辑
final boolean acquireQueued(final Node node, int arg) {
    //true,表示当前线程是被异常或者中断的,需要执行finally代码块里面的方法
    //false,表示需要出队,不会执行finally里面的方法
    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;
            }
            //若前置节点不为头节点,则挂起
            //若前置节点状态为signal,则需要挂起
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
//挂起逻辑,加上上一个方法的自旋,就算前置节点状态不为signal,最后也会被设置为signal,并且cancel节点会被跳过,实则就出队了
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    //判断前置节点状态是否为signal
    if (ws == Node.SIGNAL)
        return true;
    
    //条件成立,代表当前节点的前置节点为cancel状态,需要出队
    if (ws > 0) {
        //帮助当前节点找到前置节点状态不为cancel状态的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //找到了则将前置节点设置为signal
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

//挂起当前线程,并看看当前线程要不要中断
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

释放锁逻辑

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

unlock其实是调用了release方法

public final boolean release(int arg) {
    //尝试释放锁
    if (tryRelease(arg)) {
        //释放锁成功
        Node h = head;
        //判断当前队列是否为空,如果头节点不为空,说明队列已经创建
        //再判断当前头节点状态是否为0,不为0说明头节点的后继节点修改过此节点的状态
        if (h != null && h.waitStatus != 0)
            //唤醒下一个线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}
//前置节点也就是node释放锁成功,唤醒下一个节点的逻辑如下
private void unparkSuccessor(Node node) {
    
    int ws = node.waitStatus;
    //如果前置节点状态为signal,则修改为0,表明已经唤醒了下一个节点了
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //获取后置节点
    Node s = node.next;
    //后置节点为null,表明没有需要唤醒的节点了(两钟情况为null,一是当前节点为尾节点,二是入队的时候给打断了)
    //后置节点不为空但是后置节点为cancel状态,不需要唤醒,因为他需要出队(在shouldPark...那个方法里出队了)
    //如果符合上述两个条件之一,说明需要找到当前节点的下一个可以被唤醒的节点,也就是状态为signal
    if (s == null || s.waitStatus > 0) {
        s = null;
        //
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //如果下个节点不为空且状态是signal,则唤醒
    if (s != null)
        LockSupport.unpark(s.thread);
}

中断逻辑

//当前节点取消竞争,首先需要拿到当前节点的前置节点不为cancel的节点
private void cancelAcquire(Node node) {
    
    if (node == null)
        return;

    node.thread = null;

   //拿到需要取消排队节点的前置节点
    Node pred = node.prev;
    //如果前置节点的状态大于0,说明是cancel状态
    //那么就一直去找前置节点不是cancel的节点
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;
	
    //1.predNext为当前节点node
    //2.predNext为Wc>0的节点,即cancel节点,因为上一步找前置节点为不是cancel节点的时候跳过了cacel节点
    //但是没有把前置节点的后继指针修改
    Node predNext = pred.next;

   //将当前节点的状态设置为cancel表示出队
    node.waitStatus = Node.CANCELLED;
	/**
         * 当前取消排队的node所在 队列的位置不同,执行的出队策略是不一样的,一共分为三种情况:
         * 1.当前node是队尾  tail -> node
         * 2.当前node 不是 head.next 节点,也不是 tail
         * 3.当前node 是 head.next节点。
     */
    //如果当前节点为尾节点执行以下操作
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        //如果当前节点不是head.next并且也不是tail节点,则执行以下操作
        int ws;
        //if条件块里面就是保证前置节点的状态为signal
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            //这里是出队逻辑
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next); 
        }// 如果当前节点是head.next节点
        else {
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值