ReentrantLock的源代码解析和锁的内存语义实现

我们这一篇重点解析的是ReentrantLock,对AbstractQueuedSynchronizer(AQS)的解析会相对较少。所以,如果对AQS的原理还不是很了解的话,建议先去了解AQS的实现原理,然后再来看本篇,一些之前看不太懂的地方应该就会恍然大悟了。

1.ReentrantLock类

我们先来看看ReentrantLock类中大体包含一些什么成员变量和函数:

public class ReentrantLock implements Lock, java.io.Serializable {
    //实现同步功能的主要对象,可以从下方的函数调用中看到它有多重要
    private final Sync sync;
    //Sync是继承自AQS的内部类,在不同的场景中,可以通过实现不同的AQS方法来完成,原版注释中有一句话比较重要:使用AQS的state来表示持有锁的数目
    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer{

         abstract void lock();

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        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;
        }

        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;
        }
        ...
    }
    //实现非公平锁的静态内部类
    static final class NonfairSync extends Sync{
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    //实现公平锁的静态内部类
    static final class FairSync extends Sync{

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

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    //默认构造返回非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    }
    //带参构造可以构造非公平和公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    //加锁,由于Sync是FairSync和NonfairSync的父类,所以使用多态调用具体子类中的lock函数
    public void lock() {
        sync.lock();
    }
    //尝试图获取非公平锁
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    //释放锁
    public void unlock() {
        sync.release(1);
    }
    //创建等待队列
    public Condition newCondition() {
        return sync.newCondition();
    }
    ...
}

我们一步步来分析ReentrantLock的lock()和unlock()过程:

2. lock过程

    //ReentrantLock
    public void lock() {
        sync.lock();
    }

lock执行了sync.lock(),而Sync是FairSync和NonfairSync的父类,Sync中的lock()是一个抽象函数,说明实质是调用的FairSync和NonfairSync中的lock().

我们先以默认NonfairSync为例进行探讨,我们看到NonfairSync中的lock函数,先用compareAndSetState(0,1)利用CAS操作来试探state状态,试探是否已经有线程持有state,如果CAS操作成功,当前线程持有state,代表当前线程可以执行。如果CAS失败,执行acquire(1).

//NonfairSync
final void lock() {
    //通过cas操作试探state是否为0,为0则代表当前线程可以占有锁
    if (compareAndSetState(0, 1))
         setExclusiveOwnerThread(Thread.currentThread());
    else
         acquire(1);
}

其中,compareAndSetState()方法是在AQS中进行实现的,它调用了UnSafe中的compareAndSwapInt()方法,compareAndSwapInt()也是一个CAS操作。了解UnSafe应该知道,UnSafe类中主要的内容就是CAS操作,而它里面的CAS操作是通过调用native方法来实现的。

    //UnSafe
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

回到正题,接下来要执行的是acquire(1),而acquire()方法是在AQS中进行实现的。这个是AQS中的核心方法了,它完成的工作有同步状态获取,节点构造,加入同步队列,以及自旋等相关工作。

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

acquire()调用了tryAcquire()方法,这里用到了一种设计模式,没错,模板方法模式:将父类的一些步骤延迟到了子类中进行实现。在NonfairSync中我们可以发现实现好的tryAcquire(),然而它又调用了nonfairTryAcquire(acquires)进行实现,这个方法又在哪呢?

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

这个方法nonfairTryAcquire(acquires)在NonfairSync的父类Sync中进行了实现。重点来了…

        //Sync
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            //获取锁的状态,首先读volatile变量state
            int c = getState();
            //c等于0表示当前线程可获取锁
            if (c == 0) {
                //利用CAS操作将state置为1
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果state不为0,则判断当前线程是否拥有锁
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //如果拥有锁,则将状态位设为state+1
                setState(nextc);
                return true;
            }
            return false;
        }

至此,加锁lock过程完成。但是在执行nonfairTryAcquire()过程中还是有一些疑惑。

  • state是volatile变量,它在执行state=state+1的过程中会不会发生问题?因为我们常常讲的volatile适用的场景有:

1.对变量的写操作不依赖于当前值
2.该变量没有包含在具有其他变量的不变式中

答案是:当然没有问题。如果该线程已经拥有锁,其他线程在探测时会获取锁失败进行等待,所以当前拥有锁的线程对state+1不会发生数据不一致的问题。

值得注意的重点是:state是volatile变量,当我们每次对state进行写操作后,当前线程的工作内存中的共享变量state值会立即刷新到主内存中,保证其他线程对state状态即时可见。当线程要获取state状态时,将线程中的state状态置为无效,然后从主存中读取最新的state状态。这样保证了一个锁不会同时分配给两个线程。

3. unlock过程

unlock的过程和lock的过程其实大体是类似的,一些较为一样的地方,我们就一笔带过了,我们重点详述不同之处:

    //ReentrantLock
    public void unlock() {
        sync.release(1);
    }

调用了AQS中的release()方法

    //AQS
    public final boolean release(int arg) {
        //试图释放锁
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

调用了tryRelease()方法,和lock中调用类似,使用模板方法模式将该方法延迟到子类中实现。

        //Sync
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //如果state为0,则表示该线程将所拥有的全部锁释放完毕,恢复自由^_^
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);//释放锁的最后,写volatile变量state
            return free;
        }

4. 锁的内存语义

锁在释放的最后对volatile变量进行了写操作,在获取锁之前对volatile变量进行了读操作。释放锁的线程对volatile变量进行的写操作会立即把工作内存中的共享变量刷新到主内存中,在线程对锁进行获取时,同一个volatile变量将立即对获取锁的线程可见。

在上面的lock过程中,我们看到了CAS操作的使用,而且使用了compareAndSetState操作对state进行了更新。CAS操作是:如果当前状态值等于预期值,则以原子方式将同步状态设置为给定的更新值。可以发现CAS操作同时具有了volatile读和写的内存语义。

在上一篇博客中,我们说volatile有序性的内存语义:

volatile读之后 的操作不会被重排序到 volatile读之前
volatile写之前 的操作不会被重排序到 volatile写之后

这意味着CAS操作要兼具这两个内存语义,使得CAS前面和后面的内存操作不能重排序。那它是如何实现的?

我们通过上面的lock分析知道,CAS操作最终调用了native方法中的CAS操作。x86的处理器会调用cmpxchg指令来执行CAS操作,在多处理器情况下,还会添加 lock 前缀作为内存屏障。

lock前缀会禁止该指令与之前和之后的读和写指令重排序,并把写缓冲区的数据刷新到主内存中。

5.总结

lock()调用过程:

(1) ReentrantLock:lock()
(2) NonfairSync:lock()
(3) AbstractQueuedSynchronizer:compareAndSetState() / acquire(1)
(4) NonfairSync:tryAcquire()
(5) Sync:nonfairTryAcquire()

unlock()调用过程:

(1) ReentrantLock:unlock()
(2) AbstractQueuedSynchronizer:release()
(3) Sync:tryRelease()

参考:

猜你喜欢

转载自blog.csdn.net/tb3039450/article/details/67637887