深刻ReentrantLock 源码剖析(JDK1.8)

ReentrantLock简单使用demo以下:java

Lock lock = new ReentrantLock(); 
lock.lock();
try {
    //业务逻辑  
} finally {
  lock.unlock();
}

注:获取的锁代码要放到try块以外,防止得到锁代码异常,抛出异常的同时,也会致使一次锁的释放。释放代码必定要放到finally块中。node

** AQS **
了解java中的锁,首先的了解AQS。
AQS(AbstractQueuedSynchronizer)队列同步器。是用来构建锁或者其它同步组件的基础框架,他实现了一个int成员变量标识同步状态(更改这个变量值来获取和释放锁),经过内置的FIFO双向队列来完成资源获取线程排队的工做。
AQS能够实现独占锁和共享锁,RenntrantLock实现的是独占锁,ReentrantReadWriteLock实现的是独占锁和共享锁,CountDownLatch实现的是共享锁。安全

ReentrantLock 类结构信息以下图:并发

  • ReentrantLock 实现 Lock 和 Serializable 接口
  • RentrantLock 有三个内部类 Sync、NonfairSync 和 FairSync 类
  • Sync 继承 AbstractQueuedSynchronizer 抽象类
  • NonfairSync(非公平锁) 继承 Sync 抽象类
  • FairSync(公平锁) 继承 Sync 抽象类

** 公平锁和非公平锁 **框架

ReentrantLock 有两种实现方式,公平锁和非公平锁。ui

  • 公平锁:当前线程不马上得到锁,而是先直进入等待队列中队尾进行排队获取锁。this

  • 非公平锁:当前线程首先尝试获取一下锁(仅仅尝试一下),若是获取不到,则乖乖的进入到等待队列中去排队。线程

ReentrantLock实现公平锁和非公平锁代码以下:code

/**
     * 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();
    }

** 获取非公平锁 **orm

/**
        * 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);
       }
  1. 首先经过CAS更新AQS中的state变量来得到锁(第一次得到锁),若是获取成功则把当前线程设置为独占锁
  2. 若是是设置失败,进入到acquire方法
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  1. 首先执行tryAcquire方法,尝试得到锁。
  2. 若是获取失败则进入addWaiter方法,构造同步节点,将该节点添加到同步队列尾部,并返回此节点,进入acquireQueued方法。
  3. acquireQueued方法,这个新节点死是循环的方式获取同步状态,若是获取不到则阻塞节点中的线程,阻塞后的节点等待前驱节点来唤醒。
/**
         * 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();
            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;
        }

tryAcquire调用nonfairTryAcquire方法来第二次尝试得到锁

  1. 若是state变量为0,则进行CAS尝试更新state来得到锁,并把该线程设置成独占锁,并返回true。
  2. 若是state变量不为0,则判断当前线程是否为独占锁,若是是,则当前state+1(可重入锁),表示获取锁成功,更新state值,并返回true。这里更新state变量,不须要CAS更新,由于,当前线程已经得到锁。
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

将构造的同步节点加入到同步队列中

  1. 使用链表的方式把该Node节点添加到队列尾部,若是tail的前驱节点不为空(队列不为空),则进行CAS添加到队列尾部。
  2. 若是更新失败(存在并发竞争更新),则进入enq方法进行添加
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

该方法使用CAS自旋的方式来保证向队列中添加Node(同步节点简写Node)

  1. 若是队列为空,则把当前Node设置成头节点
  2. 若是队列不为空,则向队列尾部添加Node
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

在acquireQueued方法中,当前线程在死循环中尝试获取同步状态,

  1. 若是当前节点的前驱节点头节点才能尝试得到锁,若是得到成功,则把当前线程设置成头结点,把以前的头结点从队列中移除,等待垃圾回收(没有对象引用)
  2. 若是获取锁失败则进入shouldParkAfterFailedAcquire方法中检测当前节点是否能够被安全的挂起(阻塞),若是能够安全挂起则进入parkAndCheckInterrupt方法,把当前线程挂起,并检查刚线程是否执行了interrupted方法。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 尝试将当前节点的前驱节点的等待状态设为SIGNAL
             * 1/这为何用CAS,如今已经入队成功了,前驱节点就是pred,除了node外应该没有别的线程在操做这个节点了,那为何还要用CAS?而不直接赋值呢?
             * (解释:由于pred能够本身将本身的状态改成cancel,也就是pred的状态可能同时会有两条线程(pred和node)去操做)
             * 2/既然前驱节点已经设为SIGNAL了,为何最后还要返回false
             * (由于CAS可能会失败,这里无论失败与否,都返回false,下一次执行该方法的以后,pred的等待状态就是SIGNAL了)
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

waitStatus状态值

状态 说明
CANCELLED 1 等待超时或者中断,须要从同步队列中取消
SIGNAL -1 后继节点出于等待状态,当前节点释放锁后将会唤醒后继节点
CONDITION -2 节点在等待队列中,节点线程等待在Condition上,其它线程对Condition调用signal()方法后,该节点将会从等待同步队列中移到同步队列中,而后等待获取锁。
PROPAGATE -3 表示下一次共享式同步状态获取将会无条件地传播下去
INITIAL 0 初始状态
  1. 首先获取前驱节点的等待状态ws
  2. 若是ws为SIGNAL则表示能够被前驱节点唤醒,当前线程就能够挂起,等待前驱节点唤醒,返回true(能够挂起)
  3. 若是ws>0说明,前驱节点取消了,并循环查找此前驱节点以前全部连续取消的节点。并返回false(不能挂起)。
  4. 尝试将当前节点的前驱节点的等待状态设为SIGNAL
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

把当前线程挂起,并检查刚线程是否执行了interrupted方法,并返回true、false。

** 公平锁 **

公平锁和非公平锁实现方式是同样的,惟一不一样的是tryAcquire方法的实现,下面是公平锁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;
        }
  1. 首先获取当前锁状态,若是当前state==0(无锁),则进行获取锁操做
  2. hasQueuedPredecessors方法判断头结点是否当前线程,若是是当前线程则进行CAS更新得到锁,获取成功,把当前线程设置成独占锁。
  3. 若是不是头结点或获取锁失败则,则判断当前线程是否为独占锁,若是是,则当前state+1(可重入锁),表示获取锁成功,更新state值,并返回true。这里更新state变量,不须要CAS更新,由于,当前线程已经得到锁。
相关文章
相关标签/搜索