Lock之ReentrantLock源码解读

Lock

Lock是顶层接口,它的实现逻辑并未用到synchronized,而是利用了volatile的可见性。

public interface Lock {
    /**
     * Acquires the lock.获取锁
     * 如果锁不可用,则当前线程将被禁用以用于线程调度,并处于休眠状态,直到获得锁为止。
     */
    void lock();

    /**
     * 获取锁,除非当前线程被中断
     * <p>
     * 如果锁可用,则获取锁并立即返回。
     * 如果锁不可用,则当前线程将被禁用以用于线程调度,并处于休眠状态,直到发生以下两种情况之一:
     * 1、锁由当前线程获得
     * 2、其他线程中断当前线程,并且支持中断锁获取
     * <p>
     * 如果当前线程在进入此方法时设置其中断状态或者在获取锁时被中断,并且支持锁获取的中断,则抛出InterruptedException并清除当前线程的中断状态
     *
     * @throws InterruptedException
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 获取锁,仅在获取时它是空闲的
     * <p>
     * 获取锁(如果可用),则立即返回true;
     * 如果锁不可用,则立即返回false
     *
     * @return
     */
    boolean tryLock();

    /**
     * 如果锁在给定的等待时间内是空闲的并且当前线程没有被中断,则获取该锁
     * 如果锁可用,则返回true
     * 如果锁不可用,则当前线程将被禁用以用于线程调度,并处于休眠状态,直到发生以下三种情况之一:
     * 1、锁由当前线程获得
     * 2、其他线程中断当前线程,并且支持中断锁获取
     * 3、指定的等待时间已过
     *
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 释放锁
     */
    void unlock();

    /**
     * 返回绑定到此{@code Lock}实例的新{@link Condition}实例。
     *
     * @return
     */
    Condition newCondition();

}

 

 

ReentrantLock

  ReentrantLock 意思为可重入锁,指的是一个线程能够对一个临界资源重复加锁。

    ReentrantLock的底层是由AQS(抽象队列同步器)来实现的。

 

ReentrantLock对Lock接口的实现主要依赖了Sync(静态内部抽象类),而Sync继承了AbstractQueuedSynchronizer(AQS)

源码:

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

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

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

        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        // Methods relayed from outer class

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        final boolean isLocked() {
            return getState() != 0;
        }

        /**
         * Reconstitutes this lock instance from a stream.
         * @param s the stream
         */
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
}

 

在AQS中,定义了一个volatile int state 变量作为共享资源。

 /**
     * The synchronization state.
     */
    private volatile int state;

如果线程获取此共享资源失败,则进入同步FIFO队列中等待;如果成功获取资源就执行临界区代码。执行完释放资源时,会通知同步队列中的等待线程来获取资源后出对并执行。

更多AQS信息请看:https://www.cnblogs.com/yangyongjie/p/13847717.html 

 

非公平锁

首先默认构造是非公平锁,会出现后获取锁的线程先获得锁的情况

   public ReentrantLock() {
        sync = new NonfairSync();
    }

NonfairSync:

 1   /**
 2      * Sync object for non-fair locks
 3      */
 4     static final class NonfairSync extends Sync {
 5         private static final long serialVersionUID = 7316153563782823691L;
 6 
 7         /**
 8          * Performs lock.  Try immediate barge, backing up to normal
 9          * acquire on failure.
10       * 若通过CAS设置变量State(同步状态)成功,也就是获取锁成功,则将当前线程设置为独占线程。
11       * 若通过CAS设置变量State(同步状态)失败,也就是获取锁失败,则进入Acquire方法进行后续处理。
12          */
13         final void lock() {
14             if (compareAndSetState(0, 1))   //compareAndSetState(int expect, int update)
15                 setExclusiveOwnerThread(Thread.currentThread()); // 设置独占锁持有者为当前线程
16             else
17                 acquire(1);
18         }
19 
20         protected final boolean tryAcquire(int acquires) {
21             return nonfairTryAcquire(acquires);
22         }
23     }

nonfairTryAcquire(acquires):

 1 /**
 2          * Performs non-fair tryLock.  tryAcquire is implemented in
 3          * subclasses, but both need nonfair try for trylock method.
 4          */
 5         final boolean nonfairTryAcquire(int acquires) {
 6             final Thread current = Thread.currentThread();
 7             int c = getState();
 8             if (c == 0) {
 9                 if (compareAndSetState(0, acquires)) {
10                     setExclusiveOwnerThread(current);  // 设置独占锁持有者为当前线程
11                     return true;
12                 }
13             }
14             else if (current == getExclusiveOwnerThread()) {
15                 int nextc = c + acquires;
16                 if (nextc < 0) // overflow
17                     throw new Error("Maximum lock count exceeded");
18                 setState(nextc);
19                 return true;
20             }
21             return false;
22         }

 

公平锁,所谓的公平锁就是先等待的线程先获得锁

   static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            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) {
                // hasQueuedPredecessors() 判断当前线程前面是否有其他线程在排队
                // 有返回true,说明当前线程不是位于队列首位;没有返回false,说明当前线程位于队列首位或者队列为空
                // 只有当前线程位于首位或队列为空时才去获取锁修改同步状态值
                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;
        }
    }

 

公平锁和非公平锁总结:

 定义:

  公平锁是指多个线程按照申请锁的顺序来获取锁,线程直接进入等待队列中排队,队列中的第一个线程才能获得锁。

  非公平锁是多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待。如果此时锁刚好可用,那么这个线程可以无需阻塞直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。

  

 优缺点:

  公平锁的优点是等待锁的线程不会饿死。  缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

  非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。  缺点是出于等待队列中的线程可能会饿死,或者等很久才会获得锁。

 

 

公平锁和非公平锁的区别在于获取锁时,公平锁需要判断当前线程位于队首或者队列为空,否则没有资格获取锁。

非公平锁在获取锁时先尝试插队,插队失败再去排队。

 

 

加锁

ReentrantLock#lock

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

所以,默认执行的是NonfairSync中的lock()实现,利用Unsafe类的CAS,期望state值为0时将其值设为1,返回是否成功

    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

因此ReentrantLock的lock()方法只有在state为0时才能获得锁,并将state设为1。这样其他线程就无法获取锁,只能等待。

由于ReentrantLock是可重入锁,即在获得锁的情况下,可以再次获得锁。并且线程可以进入任何一个它已经拥有的锁所同步着的代码块。

若在没有释放锁的情况下,再次获得锁,则state加1,在释放资源时,state减1,因此Lock获取多少次锁就要释放多少次锁,直到state为0。

 

解锁

  见上面贴的AQS博客

 

 

 

重入锁和非重入锁

  重入锁:持有锁的线程可多次进入加锁的同步代码,state表示的是重入次数

  非重入锁:持有锁的线程不能多次进入加锁的同步代码

 

可重入的实现原理

Reentrant的非公平锁和公平锁的加锁都有这样一段逻辑,以非公平为例:

  ReentrantLock.Sync#nonfairTryAcquire:

final boolean nonfairTryAcquire(int acquires) {
            // 当前线程(获取锁的是当前线程)
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果锁是空闲的,直接加锁
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    // 设置独占锁的拥有者为当前线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果锁被自己持有,则state继续加1
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 否则,锁被其他线程占用,返回false
            return false;
        }       

AbstractOwnableSynchronizer(AQS继承了它):

   /**
     * 当前独占锁的拥有者线程
     */
    private transient Thread exclusiveOwnerThread;

    /**
     * 设置当前线程为独占锁的拥有者
     * @param thread
     */
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }
    
    /**
     * 返回当前独占锁的拥有者线程
     * @return
     */
    protected final Thread getExclusiveOwnerThread() {
        return exclusiveOwnerThread;
    }

  因此:可重入主要是由同步状态state和判断保存的独占锁的拥有者线程来控制实现的。因此主要还是依赖AQS

  

  state字段的状态及含义:

    1、state初始化的时候为0,表示没有任何线程持有锁。

    2、当有线程持有该锁时,值就会在原来的基础上+1,同一个线程多次获得锁是,就会多次+1,这里就是可重入的概念。

    3、解锁也是对这个字段-1,一直到0,此线程对锁释放。

 

END.

posted @ 2019-06-13 23:31  杨岂  阅读(700)  评论(0编辑  收藏  举报