ReetrantLock

675 阅读3分钟

介绍

行为与语义和synchronized一模一样。但是,ReetrantLock拓展了一些功能。

使用

使用上来说,LinkedBlockingQueue已经给了实践:ReetrantLock+Condition。一篇不错的使用文章ReetrantLock应用

阅读

ReetrantLock的阅读,核心就是AQS:AbstractQueuedSynchronizer。而AbstractQueuedSynchronizer的核心就是CLH锁队列。CLH是3个人,他们发明了CLH锁。

api上的认知点击这里

如何实现点击这里

额外补充下CLH锁的介绍点击这里

api,实现两者的阅读已经挺完整了,但需要你自己去抓核心。这里我简单理一条线,并凸显下AbstractQueuedSynchronizer中CLH锁队列的使用。这条线的顺序是lock(ReentrantLock)->acquire(AbstractQueuedSynchronizer)->tryAcquire(子类同步器实现)

lock

final void lock() {
        // 这个方法调用的理解很重要!
        // 这里涉及到的是对AbstractQueuedSynchronizer的state字段。
        // state代表的是AbstractQueuedSynchronizer同步器的同步状态,简单理解就是这个锁目前有没有被线程占有(0:没有,>1:被占有,当前线程对这个锁的重入次数)
        if (compareAndSetState(0, 1))
            // 这个涉及到AbstractQueuedSynchronizer的
            // exclusiveOwnerThread字段,代表当前持有独占锁的线程
            // 此时,lock执行完毕,不会产生阻塞。
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 所以,这个else里面肯定会产生阻塞了。
            acquire(1);
    }
        
    protected final boolean compareAndSetState(int expect, int          update) {
        return U.compareAndSwapInt(this, STATE, expect, update);
    }

acquire

public final void acquire(int arg) {
        // 这里tryAcquire放到下面一个节点讲述。
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    *** 当tryAcquire获取锁(体现到代码就是设置同步器的state为1)失败的时候,
    会先进行addWaiter再acquireQueued ***。
    
    private Node addWaiter(Node mode) {
        // 产生这个Node的理解挺重要。这里不发散,直接说下作用
        // 产生Node节点,nextWaiter字段指向mode(null),调用lock方法处的线程存入thread字段。
        // 所以,这里大家可以去看看Node到底有哪些字段。
        Node node = new Node(mode);

        // 这里是操作CLH锁队列的核心了!!!
        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                // 个人认为这里可以直接node.prev = oldTail
                U.putObject(node, Node.PREV, oldTail);
        // 这句话的作用是:将tail更新指向我们上面新增的node。
        // 通过unsafe保证并发修改的准确性。
                if (compareAndSetTail(oldTail, node)) {
                    // oldTail是局部变量,不会发生并发修改的问题。
                    // next字段是volatile类型,保证其可见性
                    // happen-before,保证对next字段的读取发生在修改之后
                    oldTail.next = node;
                    return node;
                }
            } else {
                // tail为空时,代表未进行初始化。
                // 初始化的动作其实也很简单,通过unsafe的compareAndSwapObject
                // 保证并发操作共享资源的准确性。初始化一个Node赋值给head,tail
                initializeSyncQueue();
            }
        }
    }
    
    final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            // 看到很多这种循环了,其实就是CAS操作失败自旋。
            // 当你一定需要CAS操作成功的时候,通常会以这种方式产生自旋,一直等待到CAS操作成功。
            for (;;) {
                final Node p = node.predecessor();
                
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                // 自旋锁的变种(不是不断循环,而是阻塞)实现
                // shouldParkAfterFailedAcquire根据preNode的waitstatus
                // 决定是否阻塞,不阻塞的话就进入外层的for循环即自旋了。
                // parkAndCheckInterrupt通过Unsafe.park进入阻塞
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (Throwable t) {
            cancelAcquire(node);
            throw t;
        }
    }

以上lock操作如何进入自旋,阻塞的过程已经完整。注释中关于Unsafe的描述是对其并发性的一个理解。如果想看他的lock上锁流程,可以忽略对unsafe的理解。

tryAcquire

tryAcquire的理解可以让你更好的知道:ReentrantLock中公平锁,非公平锁的区别。这里先给出区别,代码阅读就先不写了,直接去看各自实现。 公平,非公平的区别其实只存在于子同步器FairSync和NonfairSync的tryAcquire实现。具体表现就是:

  • 当state为0是,非公平锁会直接尝试去获得锁,成功返回true,这次lock就执行完毕,不产生任何自旋和阻塞。而公平锁会先看同步器中CLH队列是否已存在节点。