ReentrantLock重入锁

​一,重入锁定义
重入锁,顾名思义,就是支持重新进入的锁,表示这个锁能够支持一个线程对资源重复加锁。如果一个线程已经拿到了锁,那么他需要再次获取锁的时候不会被该锁阻塞。

举个例子,

public synchronized void test(){
    do something...
    test2();
}
public synchronized void test2(){
    do something;
}

test和test2都是同步方法,在执行之前都需要上锁,执行test获取到了锁,test里面调用test2,如果是重入锁,锁识别到获取锁的线程就是当前线程,那么直接进入。如果锁不支持重入,那么显然执行会阻塞在test2()这个位置。

当然,synchronized和ReentrantLock都是重入锁,均支持重新进入。

二,重入锁和非重入锁实现上的区别
上面算是理论,那么具体实现上如何支撑锁的重入性呢?

在队列同步器章节给出了Mutex锁的实现,这个Mutex就是一个非重入锁。

下面是他的代码逻辑,

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

class Mutex implements Lock {
    // 静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        // 是否处于占用状态
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // 当状态为0的时候获取锁
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 释放锁,将状态设置为0
        protected boolean tryRelease(int releases) {
            if (getState() == 0) throw new
                    IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // 返回一个Condition,每个condition都包含了一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    // 仅需要将操作代理到Sync上即可
    private final Sync sync = new Sync();

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

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

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

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

lock方法实际上是去调用同步器的模板方法acquire(int args),而acquire方法又会调用重写的tryAcquire(int args)方法。在Mutex重写的tryAcquire逻辑中,直接尝试用CAS获取锁,成功返回true,失败返回false。

它并没有一个判断同步状态的逻辑,假设当前线程已经是持有锁的线程,重入的时候CAS失败线程就阻塞在了这里,所以Mutex不支持锁的重入。

对比ReentrantLock的逻辑,

    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)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

而ReentrantLock的实现,是在上面基础上增加了再次获取同步状态的处理逻辑,即通过判断当前线程是否为持有锁的线程来决定获取操作是否成功,如果不是再去CAS,如果是直接将同步状态增加,相当于上锁次数+1。

既然多次上锁,那么在解锁的时候肯定也需要多次解锁,所以在tryRelease的实现上也会和Mutex不同,

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

假设已经上了n次锁(也就是锁重入了n次),那么前(n-1)次tryRelease必须返回false,只有n次解锁,同步状态完全释放了,才能说我这个锁释放成功了。

而在Mutex的tryRelease逻辑里,只需要设置同步状态是0,直接释放锁即可。

三,synchronized和ReentrantLock
这两个都支持锁的重入,只不过synchronized是隐式的获取/释放锁,隐式地重进入。而ReentrantLock的lock()和unlock()都是我们手写的,在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

均参考整理自《Java 并发编程的艺术》

posted @ 2024-03-27 15:36  YuKiCheng  阅读(151)  评论(0编辑  收藏  举报