重入锁(ReentrantLock)详解 - 决定一个人段位的是对知识的认知程度

原创
2020/02/29 20:50
阅读数 747

重入锁(ReentrantLock)详解

1、重入锁(ReentrantLock)

  • 支持重进入(表示该锁能够支持一个线程对资源的重复加锁)。

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

  • 该锁支持获取锁时的公平和非公平性选择(公平锁的效率没非公平性高)ReentrantLock默认采用非公平性。

    非公平模式会在一开始就尝试两次获取锁,如果当时正好state的值为0,它就会成功获取到锁,少了排队导致的阻塞/唤醒过程,并且减少了线程频繁的切换带来的性能损耗。

    非公平模式有可能会导致一开始排队的线程一直获取不到锁,导致线程饿死

  • ReentrantLock是通过组合自定义同步器来实现锁的获取和释放。

2、Lock接口源码解析

接口:
public interface Lock {
    /**
     * Acquires the lock(获取锁).
     */
    void lock();
    /**
     * Acquires the lock unless the current thread is(获取锁,可中断)
     * @linkplain Thread#interrupt interrupted}.
     */
    void lockInterruptibly() throws InterruptedException;
    /**
     * Acquires the lock only if it is free at the time of invocation.
     *(尝试获取锁,如果没获取到锁,就返回false)
     */
    boolean tryLock();
    /**
     * Acquires the lock if it is free within the given waiting time and the
     * current thread has not been {@linkplain Thread#interrupt interrupted}.
     * (尝试获取锁,如果没获取到锁,就等待一段时间,这段时间内还没获取到锁就返回false)
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    /**
     * Releases the lock.(释放锁)
     */
    void unlock();
    /**
     * Returns a new {@link Condition} instance that is bound to this
     * {@code Lock} instance.(条件所)
     */
    Condition newCondition();
}

3、三个内部类

抽象类Sync实现了AQS的部分方法;
abstract static class Sync extends AbstractQueuedSynchronizer {}
主要用于非公平锁的获取
static final class NonfairSync extends Sync {}
主要用于公平锁的获取
static final class FairSync extends Sync

4、ReentrantLock源码解析

  • ReentrantLock默认非公平锁

        /**
         * Creates an instance of {@code ReentrantLock}.
         * This is equivalent to using {@code ReentrantLock(false)}.
         */
        public ReentrantLock() {
            sync = new NonfairSync();
        }
    
        /**
         * Creates an instance of {@code ReentrantLock} with the
         * given fairness policy.
         *
         * @param fair {@code true} if this lock should use a fair ordering policy
         */
        public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }

     

  • ReentrantLock 中公平锁的 lock 方法

        /**
         * Sync object for fair locks
         */
        static final class FairSync extends Sync {
            private static final long serialVersionUID = -3000897897090466540L;
    
            final void lock() {
                acquire(1);
            }
           // AbstractQueuedSynchronizer
           public final void acquire(int arg) {
                if (!tryAcquire(arg) &&
                    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                    selfInterrupt();
            }
            /**
             * 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) {
                   // 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
                    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;
            }
        }

     

  • ReentrantLock 中非公平锁的 lock 方法

    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
          // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

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

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

5、ReentrantLock 和synchronized的区别

  • 两者都是可重入锁

    可重入锁:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

  • synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API

    synchronized 是依赖于 JVM 实现的,ReentrantLock 是 JDK 层面实现的。

  • ReentrantLock 比 synchronized 增加了一些高级功能

    相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:

    ①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)

  • ReentrantLock提供了一种能够中断等待锁的线程的机制

    通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。

  • ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。

  • synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。

    Condition是JDK1.5之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个Lock对象中可以创建多个Condition实例(即对象监视器)

    线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能非常重要,而且是Condition接口默认提供的。

    synchronized关键字就相当于整个Lock对象中只有一个Condition实例,所有的线程都注册在它一个身上。如果执行notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而Condition实例的signalAll()方法 只会唤醒注册在该Condition实例中的所有等待线程。


展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部