Java—多线程7 Lock2---深入理解AQS

本文详细解析了Java多线程中Lock的实现原理,重点讨论了AQS(AbstractQueuedSynchronizer)的独占式锁获取与释放过程。非公平锁的获取通过模板方法acquire(int arg),尝试使用CAS获取同步状态,失败则将线程加入同步队列。释放锁时,通过unparkSuccessor唤醒最近的等待线程以保证公平性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#Java—多线程7 Lock2—深入理解AQS

##独占锁的获取

非公平锁

1.模板方法aquire(int arg)

public final void acquire(int arg) {
    //调用tryAcquire再次尝试获取锁,如果成功直接退出
    //如果失败,首先调用addWaiter将钱财封装成结点入同步队列,
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

**tryAcquire(arg)-------:**再次使用CAS尝试获取同步状态,若成功方法直接返回,当前线程置为持有锁状态,

如果失败,调用

addWaiter(Node.EXCLUSIVE), arg)————将当前线程封装成Node结点后尾插入同步队列

//Creates and enqueues node for current thread and given mode.
//将当前的线程封装成结点入队
private Node addWaiter(Node mode) {
    	//1.将当前线程以指定模式(独占式、共享式)封装成Node结点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
    	//2.拿到当前队列的尾结点
        Node pred = tail;
    	//当前队列不为空
        if (pred != null) {
            node.prev = pred;
            //将当前节点使用CAS尾插入同步队列
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
    	//当前队列为空或CAS尾插失败是调用enq
        enq(node);
        return node;
    }

mode:表示锁时什么模式,是独占式还是共享式。

enq()————当同步队列为空时,完成队列的初始化操作,以及不断CAS将当前节点未插入同步队列

private Node enq(final Node node) {
	//不断自旋	
    for (;;) {
    //拿到尾结点
        Node t = tail;
       //当前队列为空
        if (t == null) { // Must initialize
        //完成队列初始化操作,Lazy—load
            if (compareAndSetHead(new Node()))//头结点不放数据,作为一个起始位置
                tail = head;
        } else {
            //将当前节点尾插入链表
            node.prev = t;
            //不断将当前节点使用CAS尾插入同步队列中直到成功为止
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

aquireQueued() :

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            //设置终端状态,默认为false
            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);
        }
    }

注:

1.结点从同步队列获取同步状态的前提条件:

只有前驱结点是头结点时,线程才有机会获取同步状态

if (p == head && tryAcquire(arg))

**将结点置为头结点 setHead() **

private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

2.当前线程获取同步状态失败时 调用——

boolean shouldParkAfterFailedAcquire(Node pred, Node node)

尝试将前驱结点状态改为Signal,表示当前节点应该被阻塞;否则一直在aquireQueued方法中一直自旋将此节点的前驱结点置为SIGNAL

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取前驱结点状态
    int ws = pred.waitStatus;
    //如果前驱状态为 Node.SIGNAL  (-1 当前节点的后继结点为等待状态)
    if (ws == Node.SIGNAL)
        //表示应该将当前节点阻塞
        return true;
    //前驱结点被取消
    if (ws > 0) {
        //一直向前找到节点状态不是取消状态的结点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {//前驱结点状态不大于0
        //将前驱结点状态设置为-1
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

注意:

1.在同步队列中,B 前驱结点是头结点的结点,但是B不一定可以先获取到锁。

如果现在有A线程持有锁 BCD处于等待状态

现在A线程释放锁,但是又有为入队的DEF线程和B一起竞争锁,没竞争到锁的线程继续入队

这时非公平锁

独占式锁的释放

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        //获取当前同步队列的头结点
        Node h = head;
        //当前头结点的状态不为0.
        if (h != null && h.waitStatus != 0)
            //唤醒头结点的下一个结点
            unparkSuccessor(h);
        return true;
    }
    return false;
}

unparkSuccessor() 唤醒据头结点最近的一个非空结点(保证公平性)

private void unparkSuccessor(Node node) {
	//获取头结点的状态
    int ws = node.waitStatus;
    //如果状态小于0,将状态设置为0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

 //拿到当前头节点的下一个结点
    Node s = node.next;
    //下一个节点为空,
    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;
        
        //-------------------------------------------------------
    }
    //唤醒找到的结点。
    if (s != null)
        LockSupport.unpark(s.thread);
}

private void doReleaseShared() {

    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值