并发编程之Lock锁(下)(源码分析)

本文详细解析了ReentrantLock的公平锁和非公平锁实现,通过UML图展示了其内部机制。公平锁在无锁状态下,如果队列无等待线程,当前线程可直接获取;否则需遵循队列顺序。非公平锁则允许线程直接尝试获取锁,即使队列中有等待线程。当锁释放后,会唤醒AQS队列中的头节点,抢占失败的线程会被加入队列并进行自旋等待。
摘要由CSDN通过智能技术生成

Lock

上文已经通过文字和画图大致描述了Lock的整个流程,本文就来看源码

相关UML图

在这里插入图片描述
解析,上图自然懂
在这里插入图片描述
组合了一个Sync类的对象,在构造方法里面new的
在这里插入图片描述
ReentrantLock默认是非公平锁
在这里插入图片描述
如果要new公平锁,可以通过传入参数true实现
在这里插入图片描述

Sync继承AQS,Sync其实就是干实事的,ReentrantLock的方法都是通过调用它实现锁的机制
在这里插入图片描述
具体非公平锁公平锁继承抽象类Sync
在这里插入图片描述
在这里插入图片描述

公平锁和非公平锁

公平锁和非公平锁体现是在一个锁释放后,新来的线程在临界点能否抢占到锁不是一定让AQS队列里面等待的线程抢到锁,如果能就是非公平,如果不能,就是公平的,
注意:公平与不公平不体现在AQS队列等待的线程,因为AQS队列等待的线程始终是头结点被唤醒

根据上面的UML图,可以知道ReentrantLock具体是靠sync对象工作的,而sync(父类的对象)的具体实现又分为非公平锁(NonfairSync)公平锁(FairSync),这就会导致lock方法调用的一系列的不同逻辑

  • 公平锁的逻辑
final void lock() { 
    acquire(1); //传参数1抢占1把锁. 
}
public final void acquire(int arg) { // AQS里面的方法 
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
        selfInterrupt(); 
 }
protected final boolean tryAcquire(int acquires) { 
    final Thread current = Thread.currentThread(); 
    int c = getState(); 
    if (c == 0) { //表示无锁状态 
        if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) 
        { //CAS(底层#Lock指令来加锁) -> 原子操作| 实现互斥 的判断 
            //hasQueuedPredecessors()判断是否有人在阻塞队列前面排队,
            //如果没有就返回false,就代表可以去抢占!
            //因为要保证公平嘛,如果存在排队的队列,又因为要公平,所以就不能和那些排队的队列中的线程去竞争
            setExclusiveOwnerThread(current); //把获得锁的线程保存到 exclusiveOwnerThread中 
            return true; 
        } 
    }
    //有锁状态,且当前获得锁的线程和当前抢占锁的线程是同一个,表示重入!
    else if (current == getExclusiveOwnerThread()) { 
        int nextc = c + acquires; //增加重入次数. 
        if (nextc < 0) throw new Error("Maximum lock count exceeded");
        setState(nextc); //保存state 
        return true; }
    return false; 
}
  • 非公平锁的逻辑
final void lock() { 
    //不管当前AQS队列中是否有排队的情况,先去插队 
    if (compareAndSetState(0, 1)) //返回false表示抢占锁失败 
      //成功,直接设置当前线程占有锁
        setExclusiveOwnerThread(Thread.currentThread()); 
    else acquire(1); 
}

public final void acquire(int arg) { //AQS里面的方法 跟公平锁一个逻辑这里
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt(); 
}

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

final boolean nonfairTryAcquire(int acquires) { 
    final Thread current = Thread.currentThread(); 
    int c = getState(); 
    //非公平锁这是第二次抢占
    //C==0说明是无锁状态
    if (c == 0) { 
        //注意对比,没有hasQueuedPredecessors
        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; 
}

抢占失败,加入队列并进行自旋等待

public final void acquire(int arg) { 
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 
selfInterrupt(); 
}

源码可以看到tryAcquire返回false就代表抢占锁失败,那么就会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

  • addWaiter(Node.EXCLUSIVE) -> 添加一个互斥锁的节点
  • acquireQueued() -> 自旋锁和阻塞的操作

先来看添加
在这里插入图片描述
如果CAS失败或者阻塞队列未初始化就进入enq()方法中。

enq()方法中,我们在第一次进入这个方法的时候,下面图一所示,tail和head都指向null

第一次循环,到首先会到图二,然后判断t所指向的节点是不是null,如果是的话,就用CAS更新节点,这个CAS我们可以看作:头节点head为null,我们把head节点更新为一个哨兵节点(哨兵节点就是new Node()),再将tail也指向head,就是图三了
  在这里插入图片描述
第二次for循环走到上面的else语句,将新节点的前一个节点设置为哨兵节点
在这里插入图片描述
然后就是CAS更新节点,这里CAS的意思:如果最后的节点tail指向的和t是一样的,那么就将tail指向node节点
在这里插入图片描述
最后再将t的下一个节点设置为node,下图所示,就ok了
在这里插入图片描述
再来看自旋和阻塞

//node表示当前来抢占锁的线程,有可能是ThreadB、 ThreadC。。 
final boolean acquireQueued(final Node node, int arg) { 
    boolean failed = true; 
    try {
        boolean interrupted = false; 
        for (;;) { //自旋 
            
            //begin ->尝试去获得锁(如果是非公平锁的话)
            final Node p = node.predecessor(); //获得当前结点的前置节点
            if (p == head && tryAcquire(arg)) { //如果前置节点是头结点,则自己成为了可以唤醒的结点了
            //执行tryAcquire(arg)
            //即尝试抢占锁(上面已经看过了,非公平和公平有两种实现)
            //返回true,则抢占成功,不需要等待,直接返回。 
                setHead(node); 
                p.next = null; // help GC 
                failed = false; 
                return interrupted; 
            }
            //end 
            //否则,判断是否应该让线程去阻塞(park) 
            //shouldParkAfterFailedAcquire(p, node)返回true
            //则执行后面的parkAndCheckInterrupt(通过LockSupport.park)
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) /
                interrupted = true; 
        } 
    } finally { 
        if (failed) 
            cancelAcquire(node); 
    } 
}

判断是否应该阻塞的逻辑

 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)//如果是signal状态,就让它阻塞
            return true;
        if (ws > 0) {//是Cancle状态,则直接丢弃这个结点
           
            do {
                node.prev = pred = pred.prev;//从后往前去操作,防止线程安全问题
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//把前置结点设为SIGNAL
        }
        return false;
    }

注意这个waitStatus(默认为0),它决定是否要被阻塞


        static final int CANCELLED =  1;
 
        static final int SIGNAL    = -1;
      
        static final int CONDITION = -2;
       
        static final int PROPAGATE = -3;
//ThreadB、 ThreadC、ThreadD、ThreadE -> 都会阻塞在下面这个代码的位置. 
private final boolean parkAndCheckInterrupt() { 
    LockSupport.park(this); 
    return Thread.interrupted(); 
}

unlock

public final boolean release(int arg) {
        if (tryRelease(arg)) {
        //进入if里面说明此时已经真的释放锁了(逻辑要看下面)
            Node h = head;//得到AQS队列当前的head结点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//调用unpark进行唤醒,见下面
            return true;
        }
        return false;
    }

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            
            if (Thread.currentThread() != getExclusiveOwnerThread())
            //如果不是当前持有锁的线程来操作的话就直接报监视异常
                throw new IllegalMonitorStateException();
                
            boolean free = false;
            if (c == 0) {//等于0就代表真的释放了,否则的话只是减小1,即减少1次重入数
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);//这里都不用CAS了,因为调用释放,说明肯定是互斥的了
            //只有这个线程在占用锁(前面已经检查过了)
            return free;//这里如果返回true则说明上面应该进入if里面
        }
private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)//小于0,-1表示可以唤醒状态
            compareAndSetWaitStatus(node, ws, 0);//恢复成0

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {//说明ThreadB这个线程可能已经被销毁了,或者出现异常了
        //则移除这个结点
        //从尾往前遍历
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)//查找到小于等于0的结点
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值