可重入锁ReentrantLock

介绍

可重入

重入锁ReentrantLock,顾名思义,就是支持重进入的锁,它表示锁能够支持一个线程对资源的重复加锁

同时还支持获取锁时的公平和非公平性选择(构造器决定)。

synchronized关键字隐式的支持重进入,一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得该锁

公平/非公平

锁获取的公平性问题,如果在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。

公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。

构造器决定公平锁or非公平锁

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

公平的锁机制往往没有非公平的效率高,但是,并不是任何场景都是以TPS作为唯一的指标,公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到优先满足。

原理

实现可重入

重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,存在两个问题:

  1. 线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
  2. 锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。

ReentrantLock是通过组合自定义同步器来实现锁的获取与释放,以非公平性(默认的)实现为例,获取同步状态。

加锁

ReentrantLock的nonfairTryAcquire方法:

        final boolean nonfairTryAcquire(int acquires) {
            // 获取当前线程
            final Thread current = Thread.currentThread();
            int c = getState();
            // 第一次加锁直接加锁成功
            if (c == 0) {
                // 可能有多个线程同时加锁,使用CAS保证操作的原子性
                if (compareAndSetState(0, acquires)) {
                    // 设置占据锁的线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) { // 判断当前线程是否是占据锁的线程
                // 如果是,同步状态 +1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 不是,直接加锁失败
            return false;
        }

判断当前线程是否为获取锁的线程来决定获取操作是否成功,如果是获取锁的线程再次请求,则将同步状态值进行增加并返回true,表示获取同步状态成功。

成功获取锁的线程再次获取锁,只是增加了同步状态值,这也就要求ReentrantLock在释放同步状态时减少同步状态值。

解锁

ReentrantLock的tryRelease方法:

        protected final boolean tryRelease(int releases) {
            // 同步状态 - 1
            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-1)次tryRelease(int releases)方法必须返回false,而只有同步状态完全释放了,才能返回true。

同步状态是否为0作为最终释放的条件,当同步状态为0时,将占有线程设置为null,并返回true,表示释放成功。

公平获取锁

一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。

上述非公平锁,只要CAS设置同步状态成功,则表示当前线程获取了锁。

公平锁实现如下,ReentrantLock的tryAcquire方法:

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

与非公平获取锁仅仅判断条件多了hasQueuedPredecessors()方法,即加入了同步队列中当前节点是否有前驱节点的判断,返回true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁。

公平锁与非公平锁的区别

公平性锁每次都是从同步队列中的第一个节点获取到锁,而非公平性锁可能出现一个线程连续获取锁的情况(导致有些线程一直无法获取到锁)。

公平性锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平性锁虽然可能造成线程“饥饿”,但极少的线程切换,保证了其更大的吞吐量。(所以ReentrantLock默认实现为非公平锁,保证更大的吞吐量)

你可能感兴趣的:(#,juc,java,开发语言)