ReentrantLock获得锁详细过程

本文深入解析了Java并发库中的ReentrantLock,重点介绍了公平锁和非公平锁的获取锁过程。在公平锁中,线程按顺序获取资源,而在非公平锁中,线程可能跳过队列直接尝试获取锁,这可能导致某些线程长时间等待。非公平锁在效率上有优势,但可能导致线程饥饿。通过源码分析,展示了从尝试获取锁到入队、阻塞及唤醒的过程,揭示了锁机制的内部工作原理。
摘要由CSDN通过智能技术生成

一、 预备知识

ReentrantLock

  1. ReentrantLock是可重入锁,基于cas实现
  2. ReentrantLock分为公平锁和非公平锁

二、获取锁的大致过程

  1. 公平锁:当一个线程在尝试获得锁时,如果锁的state=0,且等待队列为空则获得锁,否则进入队列尾,当持有资源的线程释放锁时唤醒队首线程
  2. 非公平锁:当一个线程在尝试获得锁时,直接尝试CAS,成功则占有锁,否则进入队列尾,当持有资源的线程释放锁时唤醒队首线程,如果队首线程获得锁成功则弹出,否则不弹出

三、非公平锁详细过程

主要源码

代码前序号和下面解释序号对应
NonfairSync源码:

//1.ReentrantLock获取锁
final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}
//2.1 非公平锁 尝试获取锁
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源码:

//2.获取锁+入队列
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
//2.2 添加node
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
}
//2.3 获取+阻塞 死循环
final boolean acquireQueued(final Node node, int arg) {
        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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

详细过程

1.先执行compareAndSetState(0,1)成功则把当前线程设置为独占线程,成功则获得锁,不成功则继续

2.1 执行acquire(1)(实际调用nonfairTryAcquire(1)),里面会再次看一下state是否为0,是的话compareAndSetState(1)将state设为1并把当前线程设置为独占线程,或者看一下当前独占线程是否是当前线程是的话state+1,返回true结束,当前线程拿到资源,否则返回false

2.2 tryAcquire(1)返回false则需要执行addWaiter(null),这个方法里面是为AQS的双向链表创建了一个node,node的Thread是当前线程,addWaiter()方法里面要更新链表尾结点,因存在多个线程一起抢夺资源的情况,所以调用了compareAndSetTail(),一次不成功的话就会for (;😉 { compareAndSetTail() };这里如果链表为空的话,会new node(),创建一个空的node,让tail和head指向它,所以链表的第一个节点永远是没有实际意义的一个node(参考算法题,此举可能是为了避免tail和head为null带来的麻烦)

2.3acquireQueued()方法中其实就是当前线程争夺锁和阻塞的一个死循环,如果当前线程的node为第二个节点,那就tryAcquire(1),否则就看前一个node的waitStatus,如果waitStatus=SIGNAL的话就表明有事的时候前一个node的线程会唤醒当前线程,当前线程就可以安心的阻塞了,等被唤醒后再去tryAcquire(1),如此往复,直到获取到锁并更新head(head的值为node,但会清空thread和prev属性),这里阻塞是用的LockSupport.park(this)。如果前一个node的waitStatus=CANCELLED的话就表明前一个节点出问题了,就向前找最近的一个waitStatus不为CANCELLED节点,然后再去for循环;如果前一个node的waitStatus=CANCELLED的话就表明前一个节点出问题了,就向前找最近的一个waitStatus不为CANCELLED节点,然后再去for循环;如果前一个node的waitStatus是其他状态的话就将其cas为SIGNAL,然后再去for循环;如果在阻塞过程中出了问题便会中止此线程。

四、公平锁过程

公平锁直接执行第二步,其他相同

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

五、优缺点

  1. 公平锁优缺点:

    优点:所有的线程都能得到资源,不会饿死在队列中。

    缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

  2. 非公平锁优缺点:

    优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。

    缺点:可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值