并发编程学习笔记之九—并发工具可重入锁ReentrantLock源码分析

在Java实现管程的方式已经有了synchronized,而synchronized进行锁升级时带来的开销也是不一个不容忽略的问题。因为Java实现了一个相对轻量级的锁ReentrantLock。但是加锁和解锁具体的如何实现的?带着疑问我们来剖析下并发工具包中Lock锁的实现原理。

下面以ReentrantLock为例分析下实现原理:

ReentrantLock核心部分是使用了AbstractQueuedSynchronizer框架(以下简称AQS框架),而AQS框架也是Java并发编程包里的基础框里。

  • 如何保证加锁和解锁的状态

 简述锁的状态机制:通过一个volatile修饰的int变量state来表示锁数量,0代表目前无线程获得锁,大于0表示线程持有锁的数据。通过CAS操作修改state的值保证原子性,根据volatile的内存语义来显示变量修改的可见性,从而是所有线程读到的state的值都是最新的。

  • 可重入锁机制

可重入锁的机制的实现原理也是volatile修饰的int变量state来实现的,当前线程持有锁时,state会在原来的基础上加1,来表示持有锁的数量。(注:必须是同一线程重复获取锁)。

  • 队列同步器CHL机制

在AbstractQueuedSynchronizer中实现了Node的内部类,也是这是队列同步器CHL的主要实现元素。Node主要为了实现条件队列(用于独占模式,由单向的链表实现),等待队列(用于独占模式来共享模式,由双向链表实现),理解这两种队列的实现方式,对于阅读源码有很大帮助。

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        //用于共享模式实现节点(readLock)
        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;
        //节点状态
        volatile int waitStatus;

        //等待队列的前继节点
        volatile Node prev;

       //等待队列的后继节点
        volatile Node next;

        //节点中的线程
        volatile Thread thread;

        //在等待队列中,用来判断是独占模式还是共享模式
        //在条件队列中节点的后继节点
        Node nextWaiter;

        //判断是不是共享模式
        final boolean isShared() {
            return nextWaiter == SHARED;
        }
        //获取节点的前驱节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        //初始化节点用户等待队列
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }
        //初始化节点用于条件队列
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
  • 可重入锁的实现机制: 2.第一次cas操作,也是第一加锁的动作,如果成功,表示该锁是第一次加锁,如果失败,可能存在两种情况,(1) 两个线程在竞争同一把锁,(2)锁早已经别持有,也许是自己也可能是其他线程。 3.首次加锁失败,在读state的值,这里又会有三种情况,state是0锁已经放开了,会尝试再次加锁;锁没有放开但是确实当前线程持有的锁,直接修改锁持有的数据,这也是可从重入锁的实现机制,锁既没有释放,也不是当前线程持有的锁,那就直接返回false加锁失败。

在ReentrantLock源码中,支持公共锁和非公平锁:

默认是创建非公平锁,根据参数可创建公平锁。

//支持创建公平锁
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
//创建非公共平锁
public ReentrantLock() {
        sync = new NonfairSync();
    }

NonfairSync(非公平锁)和FaifSync(公平锁)是ReentrantLock的内部类,是锁机制的主要实现,两个内部类都是继承内部类Sync,而Sync继承了AbstractQueuedSynchronizer。所以ReenTrantLock最终锁的实现机制还是通过AQS框架实现。下面就来看看加锁和解锁的操作: 

//加锁
lock();
//解锁
unlock();

 以非公平锁为例分析源码:

  • 加锁操作:
  1. 非公平锁简单暴力直接尝试原子操作CAS获取锁(修改state值为1),加锁成功直接结束。加锁失败的原因主要有两种:1(1) 两个线程在竞争同一把锁,(2)锁早已经别持有,也许是自己也可能是其他线程,加锁不成功执行 2、方法acquire()
  2. 加锁失败后的处理:如果是因为锁已经被获取进入了加锁失败的处理逻辑,但是此时可能会出现一种情况就是已经持有所的线程释放了锁,所以为了避免无用的浪费时间,需要重新确认是否能获取到锁。即调用Sync的tryAcquire(int acquires)方法执行  2-1.nonfairTryAcquire(int acquires),如果nonfairTryAcquire(int acquires)返回true表示加锁成功,否则调用2-2acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法将线程加入阻塞队列中;
  3. 2-1.nonfairTryAcquire(int acquires)方法中获取当前线程和state值,如果state的值为0,表示没有现在获得锁,将当前现在置为占用线程,返回true;当state不为0时,表示此时有线程已经得到锁,如果此时线程为占用线程,表示此线程已获得锁,则继续修改state的值继续+1,返回true。这也就是可重入锁的实现机制。否则返回false。
 /**
  * Performs lock.  Try immediate barge, backing up to normal
  * acquire on failure.
 */
    //1.加锁方法
    final void lock() {
        通过原子操作获取锁(修改state值为1)
       if (compareAndSetState(0, 1))
            @2将当前线程设置为占用线程
            setExclusiveOwnerThread(Thread.currentThread());
       else
            2.加锁失败后的处理:加锁失败后尝试获取锁,如果仍然获取不到加入到等待队列中
            acquire(1);
        }
  //2.加锁失败后的处理:加锁失败后尝试获取锁,如果仍然获取不到加入到等待队列中
    public final void acquire(int arg) {
       //尝试再次加锁(调用子类方法)
       if (!tryAcquire(arg) &&//2-1
          acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//3-1
          selfInterrupt();
    }
    //2-1尝试加锁
     protected final boolean tryAcquire(int acquires) {
            //非公平锁尝试加锁
            return nonfairTryAcquire(acquires);
     }
     //2-2非公平锁尝试加锁
     final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取state值
            int c = getState();            
            if (c == 0) {
                //如果状态值为0,通过原子CAS操作去修改状态值,返回
                if (compareAndSetState(0, acquires)) {    
                    设置占用线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //判断当前线程是否占用线程(可重入锁实现机制)
            else if (current == getExclusiveOwnerThread()) {
                //状态state累加
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //回写state
                setState(nextc);
                return true;
            }
            return false;
        }
  1. 3-1如果2-1返回的是true表示加锁成功;返回false则继续执行加入等待队列的操作。在acquireQueued()之前会先执行3-2addWaiter(Node.EXCLUSIVE), arg)加入等待队列成功,返回当前尾节点。继续执行3-1方法,进入循环,获取当前尾节点的前驱节点,如果前驱节点为头节点,继续尝试加锁,加锁成功直接,将尾节点清空,返回。加锁失败,执行3-3方法shouldParkAfterFailedAcquire。
  2. 3-2首先将线程加入到节点Node中,判断当前对列中尾节点是不为空的情况,通过原子操作CAS将节点Node追加到队列的尾部,修改指针;如果节点为空,则调用enq方法。通过自旋,首先再次判断尾节点为空,通过原子性操作CAS设置一个空的头结点(哨兵的作用),继续循环,如果尾节点不为空,通过原子性操作CAS将当前线程追加到尾节点,返回尾当前尾节点。继续执行3-1
  3. 3-3进入shouldParkAfterFailedAcquire方法,首先获取前驱节点的信号,信号为Node.SIGNAL,表示现在目前处于阻塞中,直接返回。信号>0,表示前驱节点线程以中断或超时,舍弃前驱节点,直到获取有效节点重新规划队列节点。其他状态通过原子操作CAS设置前驱节点状态为Node.SIGNAL阻塞。最后执行parkAndCheckInterrupt方法调用 LockSupport.park方法阻塞线程,返回线程中断位,结束。
/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
//3-2加入到等待队列中,此时mode为Node.EXCLUSIVE表示是独占模式
private Node addWaiter(Node mode) {
    //设置含有当前线程的节点,
    Node pred = tail;
    //判断尾节点不为空,表示当前已经有节点在同步队列中不为空,调整队列结构
    if (pred != null) {
        //将当前节点追加到尾节点后
         node.prev = pred;
        //通过原子CAS操作,将当前节点追加到尾节点后
         if (compareAndSetTail(pred, node)) {
             pred.next = node;
             return node;
          }
     }
      //尾节点不为空的操作,说明当前等待队列中是空的
      enq(node);
     return node;
}
/**
 * Inserts node into queue, initializing if necessary. See picture above.
 * @param node the node to insert
 * @return node's predecessor
 */
 //没有尾节点情况
 private Node enq(final Node node) {
    //通过自旋
    for (;;) {
        //判断尾节点为空
        Node t = tail;
        if (t == null) { // Must initialize
           //通过原子性操作CAS设置一个空的头结点(哨兵的作用)
           if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            //如果尾节点不为空,通过原子性操作CAS将当前线程追加到尾节点
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                //返回当前线程节点
                return t;
            }
        }
     }
  }
//3-1此时占有线程可能已经执行结束,所以此时选择继续尝试加锁,加锁成功,继续进入循环,加锁失败阻塞线程
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)) {
                    //设置首节点,请除线程引用,清除原来的head引用(首节点表示当前获取锁的节点)
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //3-3加锁失败,修改等待节点状态,修改成功,阻塞线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())//阻塞队列
                    interrupted = true;
            }
        } finally {
            if (failed)
                //线程超时或异常会执行,主要清除中断或超时的等待队列的线程节点
                cancelAcquire(node);
        }
    }
    //3-3修改节点状态,修改成功阻塞
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;//获取前驱节点的状态(信号)
        if (ws == Node.SIGNAL)//信号为-1,表示现在目前处于阻塞中,直接返回
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {//信号>0,表示前驱节点线程以中断或超时,舍弃前驱节点,知道获取有效节点重新规划队列节点
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            //ws默认值是0无状态或者PROPAGATE,原子操作CAS设置前驱节点状态为Node.SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

    //调用 LockSupport.park方法阻塞线程,返回线程中断位
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
  • 解锁:
  1. 调用2)sync.release(1);
  2. 进入release方法首先执行tryRelease修改锁状态,锁状态state为0时,置空占用线程(重入锁修改状态直到为0),回写state状态。如果修改锁的状态成功则开始唤醒阻塞线程3)unparkSuccessor(Node node)
  3. 首先获取头节点,判断头节点的信号,如果头节点的ws<0则获取头节点后继节点,后继节点不为空,且信号为中断或者超时状态时,循环遍历后继节点,指定找到有效后继节点,最后//唤醒线程LockSupport.unpark(s.thread);
    //1.解锁
    public void unlock() {
        sync.release(1);
    }
    //2.修改锁状态
    public final boolean release(int arg) {
        //修改锁状态
        if (tryRelease(arg)) {
            Node h = head;
            //当头节点不为空,头节点信号存在状态(表示等待队列中存在等待线程)
            if (h != null && h.waitStatus != 0)
                //叫醒阻塞线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    } 
    //尝试修改锁状态
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        //非占用线程阻断
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        //锁状态state为0时,置空占用线程(重入锁修改状态直达为0)
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
         }
         setState(c);
         return free;
    }

    /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    //3)叫醒阻塞线程
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        //获取头节点信号
        int ws = node.waitStatus;
        if (ws < 0)
            //原子操作CAS设置首节点信号为0
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        //获取头节点后继节点
        Node s = node.next;
        //后继节点不为空,且信号为中断或者超时状态时,循环遍历后继节点,指定找到有效后继节点
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //唤醒线程
            LockSupport.unpark(s.thread);
    }

    

思考公平锁和非公平锁的区别: 

非公平锁首先是尝试加锁,如果加锁成功即返回,而公平锁,先是去判断锁是否被持有,再判断是否队里中已经有线程等待,如果没有才尝试修改锁的状态。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值