JUC中的ReentrantLock

ReentrantLock中的lock和unlock

        我们在使用并发编程大师Doug Lea提供的显示锁lock的时候,短短的一个lock和unlock即可实现元老级锁synchronized的功能,并且比其更灵活,功能更丰富。我们当然会好奇的发问,他到底是怎么做到的?Doug Lea到底有多牛?

    1.lock方法

            ReentrantLock是通过AQS实现的,在说明lock方法之前有必要提供下面这个继承图。在ReentrantLock中有三个内部类Sync、FairSync、NonfairSync,他们为ReentrantLock提供锁机制。

   

lock方法就是调用了Sync中的抽象方法,具体实现要看new ReentrantLock(boolean)指定的是公平锁还是非公平锁,true为公平锁,false为非公平锁,默认是false。下面是源码:

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

public ReentrantLock() {
        sync = new NonfairSync();
    }
 public void lock() {
        sync.lock();
    }

要使用lock必须得重写AQS的tryAcquire()方法,因为AQS并没有实现tryAcquire,如下:

protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

我们这里以非公平锁为例来说明,下面是非公平锁对tryAcquire的实现:

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;
        }

好,到这里准备工作做完了,下面就对非公平锁lock实现原理加以说明。首先要弄懂lock()到底会有什么效果,如果成功获取锁,执行临界区代码,并且有可能再临界区还需要获取同一把锁,这就是锁重入;如果失败,进入同步队列park,等待唤醒。在获取锁的时候会调用AQS的acquire方法,调用路径和acquire源码如下:

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

addWaiter()主要是将node加入队列(双向链表),并不会改变waitstatus。

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;
    }

acquireQueued()主要用途:死循环。如果当前node是head后一个节点就尝试获取一次锁,失败,就设置waitstatus=-1,使node节点park阻塞。

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);
        }
    }

注意:双向链表的头节点代指拿到锁的节点,但其thread属性为null,永远为null。每个节点的waitstatus状态保存在前驱节点上。

2.unlock方法

  调用路径

release方法源码:

 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

release源码中的tryRelease()方法由nonfairSync实现,源码如下:

protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

tryRelease()的作用:state减1,state如果等于0,setExclusiveOwnerThread(null)。返回值,如果state不为0,返回false,反之返回true。里面包含重入情况。

 


目前为止,我们粗略的理清lock和unlock到底都干了些什么,下面是对两者在实际使用中是如何配合的,有哪些细节进行阐述。

从两种情况说明:头次抢锁时,获取锁成功和获取锁失败

1.获取锁成功

         指的是在众多线程中第一个抢到锁,这时它不需要进入同步队列。获取锁成功的条件:state=0或者exclusiveOwnerThread为node(表示当前线程)。如果state为0,compareAndSetState(0, 1)原子设置state为1,setExclusiveOwnerThread(current)设置为当前线程,返回true,执行临界区代码。如果state不为0,current == getExclusiveOwnerThread(),表示重入,那么state再加1,返回true,执行临界区代码。不管哪种情况,执行完代码后都要释放锁,释放锁很简单,state减1,并setState,如果state为0,setExclusiveOwnerThread(null)返回true,这时就要唤醒head下一个节点,设置waitstatus=0,让其在park的地方苏醒,继续执行acquireQueued方法死循环的部分(尝试获取锁,获取不到设置waitstatus=-1,park);如果不为0只是setState,返回false。

2.获取锁失败

          获取锁失败,说明有线程已经占用该锁。此时state不为0,并且exclusiveOwnerThread不为node(当前线程)。先进入同步队列,要是当前线程的前驱节点是Head,尝试获取锁,成功的话,设置为Head,返回,执行临界区代码;如果获取锁失败,设置waitstatus=-1,park。什么时候被唤醒呢?在拿到锁的线程执行完临界区代码,调用unlock,执行unparkSuccessor的时候唤醒。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值