java.util.concurrent 之 可重入锁ReentrantLock

这次介绍一下以AQS为模板的一个简单的锁的实现:ReentrantLock——可重入锁

首先解释两个字:重入。重入就是重新进去,进去哪里?进去被ReentrantLock锁住的代码块。被为什么说这个锁时可重入的,是因为这个锁有一个性质,就是在这个锁已经被当前线程获取的情况下,这个线程再次尝试获取时就不用重新设置锁状态。我们知道,设置锁状态是一个CAS(compare and set)操作,这个操作是性能比较低下的。对于不可重入的锁,如果在一个循环中写了个lock,那么每次循环都去执行这个设置锁状态的CAS操作,性能低下。可重入锁就是解决了这问题。

我说的肯定是看不太懂,还是看代码,从我们使用的lock方法来看:

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

可以看到,他是把这个获取锁状态的动作委托给一个叫sync的类来实现的。

这个sync是什么类呢?他是一个继承了AbstractQueuedSyncronizer的内部类,也就是说,他有了最基本的同步功能,会使用队列来管理等待锁状态的线程。

static abstract class Sync extends AbstractQueuedSynchronizer

他也有几个方法,不过抽象类,方法总是给子类使用的,Sync这个类有两个子类:NonfairSync 和 FairSync,分别是非公平的锁和公平的锁。

解释一下公平和非公平:
现在我们知道ReentrantLock的锁功能委托给了其内部类Sync,而Sync又继承了队列同步器AQS,他的子类要获取锁,必然是通过此种方式

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

当然这个tryAcquire(int arg)方法是需要Sync的子类来实现的。我们先来看默认的非公平锁:

public ReentrantLock() {
        sync = new NonfairSync();
    }

他的lock()方法:

 /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

也是增加了一重检查,先看锁状态state,以期望值0,目标值1对AQS的state进行CAS实施CAS操作,如果失败(期望值不为0),则尝试以AQS模板类的方法尝试获取锁,上面已经讲到了,现在再写一遍,因为很重要:

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

这个方法描述一个过程,本身不体现公平与非公平,是否公平需要由子类自己实现的tryAcquire(int arg)来体现,我们来看看非公平锁的tryAcquire(int arg)的实现

protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
/**
         * 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();
            //获取AQS中的队列的同步状态
            int c = getState();
            //如果没有线程占有锁,就获取锁
            if (c == 0) {
                //以CAS操作设置锁状态
                if (compareAndSetState(0, acquires)) {
                    //设置当前线程为独占线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果尝试获取锁的线程是独占了这个锁的线程
            else if (current == getExclusiveOwnerThread()) {
                //让他重入这个锁
                int nextc = c + acquires;
                //如果这个锁在等待什么,也就是c = -2 的情况下,就不能获取锁了
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //获取锁成功了之后把锁状态更新(CAS操作)
                setState(nextc);
                return true;
            }
            return false;
        }

到现在我们还是不知道上面这段代码哪里体现了不公平,不急我们来看看公平锁的实现,对比一下就晓得了:

 /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取锁状态
            int c = getState();
            //如果锁状态为初始状态
            if (c == 0) {
                //判断线程是否在队列中处于头部位置
                if (isFirst(current) &&
                    //CAS修改锁状态
                    compareAndSetState(0, acquires)) {
                    //将当前线程设置为独占线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果当前线程就是独占线程
            else if (current == getExclusiveOwnerThread()) {
                //独占次数+1
                int nextc = c + acquires;
                //如果此时线程在等待某condition,此时线程的c = -2(CONDITION状态)
                if (nextc < 0)
                    //抛出无法再有更多的线程获取该锁
                    throw new Error("Maximum lock count exceeded");
                //将锁状态设置为独占次数
                setState(nextc);
                return true;
            }
            return false;
        }

我们可以看到对比非公平锁,公平锁的 tryAcquire 只是增加了一个 isFirst(current) 的判断,即,公平所要求线程按照CLH队列的原则来依次执行。
举个例子,有一个锁,锁住了若干线程,而且,持有这个锁的线程已经执行完毕,并对后续的线程进行唤醒(unparking),但是后续的线程去执行其他代码了,或者再等待其他condition,并没有在自旋地等待锁的释放,所以即便这个锁就处于没人用的状态,这对性能是极大地浪费。因此非公平锁就取消了这个判断,使得线程在锁可用的情况下,不用管队列的先进先出原则直接获取锁。

实践证明,非公平锁在大部分情况下的性能要优于公平锁。

当然,虽然 tryAcquire 这个方法有公平与非公平之分,但是在其继承的AQS的自旋地等待锁的 acquireQueued(final Node node, int arg) 方法中仍然是公平的,这个不知道为什么这样设计。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值