源码解析ReentrantLock上锁过程

ReentrantLock上锁过程

当面试官问执行了ReentrantLock锁是怎么上锁时,可以这样回答

1、首先创建了一个ReentrantLock锁时默认是使用的非公平锁,当执行lock的上锁方法时就会调用ReentrantLock内部的非公平锁的实现对象的lock方法。这个非公平锁对象继承了AQS(AbstractQueuedSynchronizer)这个抽象类,这个抽象类中包含了一个锁对象可能会用到的大多数方法(后面有时间再跟更新)

ReentrantLock默认以非公平锁为实现方式,当这样的对象执行lock方法时

   	ReentrantLock lock = new ReentrantLock();
    lock方法其实是ReentrantLock 对象内部的sync对象的lock方法
    lock.lock();
  	这里执行了sync的lock方法,此时的sync对象为NonfairSync
    public void lock() {
        sync.lock();
    }

NonfairSync的结构如下

  static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
         这里实现了上锁的方法,下面的tryAcquire也是用来上锁,由acquire调用
         上面的sync.lock调用的就是这个方法
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

上面的lock方法第一次cas上锁失败后开始执行acquire方法

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这里进来先执行tryAcquire方法,就是上面的非公平锁实现NonfairSync 中的方法,进入到这个方法调用的nonfairTryAcquire方法中看看

 final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            这个getState是父类中的父类AQS中的方法,用来获取当前锁标识state变量的值,0 为没有被线程持有 大于0标识被某个线程持有,数值为重入次数
            int c = getState();
             若没有线程持有,尝试加锁,这里已经是第二次尝试加锁了
             为什么要重新尝试一次,是因为有可能在第一次上锁失败后持有锁的线程就把锁释放了。
            if (c == 0) {
             这里的逻辑和NonfairSync中的lock方法逻辑相同,若加锁成功把exclusiveOwnerThread变量设为当前获取锁的线程,这个变量是AQS中的,被Sync继承
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            若上锁失败就检查当前持有锁的线程是不是自己,若是自己那就是重入锁,给state变量加上1,并返回上锁成功,否则返回上锁失败
            else if (current == getExclusiveOwnerThread()) {
                c是state变量值,acquires是调用方法传的参数,值为1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }


	setstate方法如下
   protected final void setState(int newState) {
        state = newState;
    }

此时回到acquire方法

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

若tryAcquire方法返回false,即上锁失败,后面就需要把当前线程封装为一个Node节点添加到AQS中的同步队列中去

Node节点的结构如下,省去了构造方法

static final class Node {

        static final Node SHARED = new Node();
 
        static final Node EXCLUSIVE = null;

   		这四个值是下面的waitState的四个枚举值,用于标记这个封装为Node节点的线程的状态(被中断,处于同步队列中,处于等待队列中)
   		
        static final int CANCELLED =  1;线程被中断,若在同步队列中遇到这样的节点需要丢弃

        static final int SIGNAL    = -1;等待被唤醒,加入到同步队列中的初始转态,也是正常状态

        static final int CONDITION = -2;处于条件等待转态,位于等待队列中的节点状态

        static final int PROPAGATE = -3;
		
        volatile int waitStatus;线程状态,就是上面四种值中的一个
        
        volatile Node prev;指向同步队列的前一个节点

        volatile Node next;指向同步队列的后一个节点

        volatile Thread thread;Node节点封装的线程

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

接着看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)这个方法
首先进入到addWaiter(Node.EXCLUSIVE)中

 这里的方法已经是在AQS中了
 private Node addWaiter(Node mode) {
 		把当前线程封装到Node节点中
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        这里判断如果同步队列的最后一个节点不为空,就把node节点添加到同步对列的尾部,这里也是cas加入的
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        到这里说明同步队列为空或者cas添加到队尾失败
        enq(node);
        return node;
    }

addWaiter()方法的目的是把节点添加到队尾。

看看enq(node)方法

  private Node enq(final Node node) {
  		这里是个死循环,不停的cas加入队尾直至成功入队
  		
        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;
                }
            }
        }
    }

enq(node)方法的作用就是在第一次加入队尾失败后,循环尝试着把节点加入到同步队列的尾部,当队列为空或者第一次加入队尾失败时会执行(有多线程竞争的情况第一次加入可能会失败)。

再次回到acquire方法

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

此时addWaiter(Node.EXCLUSIVE)方法也已经完成,看看acquireQueued()方法

 	这个也是AQS中提供好的方法
 	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);
        }
    }



若这个方法返回true说明node的前驱节点是一个SIGNAL状态(等待唤醒状态)的节点,需要把node节点中的线程挂起(这里就是把新加入的节点挂起)
 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        如果前面的节点是处于等待获取锁状态就返回true
        if (ws == Node.SIGNAL)
            return true;

		判断前一个节点是否是已终止状态,如果是就往前找,找到为SIGNAL状态的节点
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
        	把当前节点的前继节点置换为一个状态为SIGNAL的节点
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

把当前线程挂起并返回线程中断状态
  private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

这个方法做的内容就是从同步队列的最后一个节点开始往前遍历,把已经取消的线程节点从队列中删除,然后把新加入同步对列中的线程挂起。

至此上锁过程就结束了

总结一下这个过程:
1、当执行了ReentrantLock.lock()后先使用unsafe的cas给AQS中的变量state置换为1(在ReentrantLock中是Sync对象继承的AQS),若上锁失败就进入到acquire方法中。
2、进入到这个方法后执行tryAcquire(arg)方法,先再次尝试获取锁(进行cas操作),若失败就判断当前获取锁的线程是不是自己,如果是自己就对state加一并返回true,如果不是就返回false。
3、然后执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)就把自己封装为一个Node对象进行加入同步队列的操作。
4、addWaiter(Node.EXCLUSIVE)这个方法会创建一个Node节点把当前线程存进去,并添加到同步队列的队尾(同样cas添加)。
5、acquireQueued()这个方法是进行同步对列的整理,沿着新加入的节点往前面找,如果前面的节点转态是已取消的(waitState值为1)就跳过前面节点往前找,直到找到状态为SIGNAL或者为CONDITION 状态的节点,把新节点的前继节点设置为这个节点,这个操作相当于删除了新节点前面的被取消的节点。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值