ReentrantLock学习

基于独占模式的ReentrantLock(JDK1.8)

顶层接口

public interface Lock {
    // 加锁
    void lock();
    // 可打断的锁,当在获取不到锁等待时,可以被打断,直接返回
    // synchronized是不支持的,不会被打断
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    // 在time时间内尝试加锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    // 多个线程可以在不同的条件变量里等待
    Condition newCondition();
}

关键变量

private final Sync sync;

这个变量是用AQS同步器来实现的。提供了两种实现,一种是公平锁,一种是非公平锁。默认构造器是用了非公平锁。这里需要深刻理解公平和非公平的含义。

  • 公平锁:我们现实世界里,遵循先来后到的原则才叫公平。每个线程来了,要先到后面去排队,不能直接去抢锁,这样每个线程都有可能获得锁。
  • 非公平锁: 新来的线程先不去排队,直接和队列里的线程去抢锁,这样自然不公平的。但是性能好,新来的线程有一定概率是立刻获得锁的,但是可能造成队列里的线程很长时间都得不到锁。

原理直接源码

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() {
            if (compareAndSetState(0, 1)) // 线程来了,直接进行CAS去修改状态变量的值
                setExclusiveOwnerThread(Thread.currentThread());
            else // 修改失败,才会去到AQS的加锁流程,尝试加锁 -》 入队
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
 static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() { // 加锁操作直接调用AQS的加锁方法,尝试加锁的方法在下面
            acquire(1);
        }

        /**
         * 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 (!hasQueuedPredecessors() && // 队列里面没有线程去等待获取锁直接CAS设置锁,如果有的话就返回false了,说明AQS尝试加锁失败了,开始正式入队并加锁。
                    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; // AQS队列里面有线程,而且已经上锁了并且锁的所有者不是当前线程
        }
    }

关键方法

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

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout); // 这种逻辑运算的函数调用简直NB啊,tryAcquire失败,就正式的加锁
    }


private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold) // 还有剩余时间, 直接睡眠,等待锁释放的时候或者是时间到了就唤醒
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

总结

  • 宏观上来说,ReentrantLock在Java语言层面实现了一把锁,我们作为Java开发者, 可以去扩展,对我们更友好。
  • 场景上来说,ReetrantLock支持公平锁和非公平锁,支持可打断的锁,支持一定时间内的加锁操作
  • 原理上来说,ReentrantLock基于AQS, 使用状态变量waitStatus + CAS + 队列的来管理线程。

个人的思考

今天分析了ReentrantLock,发现同步器AQS才是重点,他为我们提供了一种抽象的框架,上层应用已经为我们写好了加锁的方法,唯一不同的是tryAquire方法的实现,这就是模板方法的应用,把不容易变的抽象到父类,形成一个模板,子类重写模板中的容易变的方法就好了。

posted @ 2021-08-01 11:13  FizzPu  阅读(23)  评论(0编辑  收藏  举报