JUC中锁的实现的一点点理解

关于JUC的讲解,下边的博客写的很详细。

http://wangkuiwu.github.io/categories/#java

  以下是自己学习JUC时,关于JUC锁的一点粗浅的理解。主要是ReentrantLock。

在JDK的JUC包中,大量的工具类使用到了锁,锁分为独占锁(exclusive lock)和共享锁(shared lock),独占锁又分为公平和非公平锁。这些锁都是基于一个核心类叫AQS(AbstractQueuedSynchronizer),而AbstractQueuedSynchronizer类里边有使用到CLH等待队列(队列的节点时内部类Node)和一个整形类型的叫state的属性字段来控制锁的状态。其中AQS包含head和tail的属性字段,分别指向队列的头和尾。对于独占锁(排他锁),头节点是当前持有锁的线程。此外,AQS同时也会用到乐观锁用到的CAS(CompareAndSwap)机制去给state字段赋值。

另外,线程在通过ReentrantLock.lock()获取锁失败而被block住时,是通过LockSupport.part()方法将线程设置为WAITING状态的(或者说将线程阻塞住),而解锁时,是通过LockSupport.unpark()方法解锁。

AbstractQueuedSynchronizer中的state字段以及CLH队列的head,tail

   /**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
    private transient volatile Node head;

    /**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
    private transient volatile Node tail;

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

 AbstractQueuedSynchronizer.Node源码

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /** waitStatus value to indicate thread has cancelled */
    static final int CANCELLED =  1;
    /** waitStatus value to indicate successor's thread needs unparking */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * waitStatus value to indicate the next acquireShared should
     * unconditionally propagate
     */
    static final int PROPAGATE = -3;

    /**
     * Status field, taking on only the values:
     *   SIGNAL:     The successor of this node is (or will soon be)
     *               blocked (via park), so the current node must
     *               unpark its successor when it releases or
     *               cancels. To avoid races, acquire methods must
     *               first indicate they need a signal,
     *               then retry the atomic acquire, and then,
     *               on failure, block.
     *   CANCELLED:  This node is cancelled due to timeout or interrupt.
     *               Nodes never leave this state. In particular,
     *               a thread with cancelled node never again blocks.
     *   CONDITION:  This node is currently on a condition queue.
     *               It will not be used as a sync queue node
     *               until transferred, at which time the status
     *               will be set to 0. (Use of this value here has
     *               nothing to do with the other uses of the
     *               field, but simplifies mechanics.)
     *   PROPAGATE:  A releaseShared should be propagated to other
     *               nodes. This is set (for head node only) in
     *               doReleaseShared to ensure propagation
     *               continues, even if other operations have
     *               since intervened.
     *   0:          None of the above
     *
     * The values are arranged numerically to simplify use.
     * Non-negative values mean that a node doesn't need to
     * signal. So, most code doesn't need to check for particular
     * values, just for sign.
     *
     * The field is initialized to 0 for normal sync nodes, and
     * CONDITION for condition nodes.  It is modified using CAS
     * (or when possible, unconditional volatile writes).
     */
    volatile int waitStatus;

 

这里以ReentrantLock源码来简要分析以下。

  在ReentrantLock中,有一个Sync类型的成员变量sync,Sync是定义在ReentrantLock静态内部类。而Sync是继承AQS(AbstractQueuedSynchronizer)的。

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;

    ...

同时,在ReentrantLock中又定义了Sync的两个子类 - FairSync和NonfairSync,就是公平锁和非公平锁。

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

    ...
    ...


    /**
     * 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() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

 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利用AQS(AbstractQueuedSynchronizer)的CLH队列和CAS机制来实现CLH等待队列中的锁按照FIFO方式公平的获取锁还是采用非公平的方式。对于公平锁,在锁被当前持有锁的线程通过lock.unlock()方法释放时,会将处于CLH等待队列中头节点(代表当前持有锁的线程)的后边,处于等待状态的第一个节点所代表的线程给唤醒(通过LockSupport.unpark方法),使该线程处于Runnable状态。这样当操作系统做线程切换时(会按照一点算法随机的轮询所有处于Runnable状态的线程,来决定下一步运行哪个线程),如果该被激活的线程被轮询到,就可以从上次停止的地方(LockSupport.park()所在的地方)继续执行,因为AQS使用自旋的方式(具体参考AQS的acquireQueued()方法),该线程会尝试再次获取锁,此时会成功,并往下执行执行。

而对于那些处在队列后边的线程,由于他们没有被之前持有锁的线程在释放锁时调用LockSupport.unpark()去给他们唤醒,所以他们还是处于WAITING状态,是没有办法获取到操作系统的临幸的,因为操作系统在做线程轮询时,只会在那些处于RUNNABLE的线程间切换。

对于非公平锁,在获取锁时与公平锁方式不同,他不会检查在CLH队列中,是不是有排在自己前边的节点,而是直接尝试获取锁。并且非公平锁的锁释放方式根公平锁是一样的。

这样就有一个疑问,在当前持有锁的线程释放非公平锁的时候,只有队列中头节点后边的第一个有效线程会被unpark,那其他节点还是处于WAITING状态,根本就不会获取到CPU的轮询,那这不是根公平锁一样的么?难道非公平锁只是在所有等待状态的线程都被中断唤醒,从而都有机会获取锁时才体现非公平的特征?

 

你可能感兴趣的:(#,MultiThread&JUC)