图解AQS原理之ReentrantLock详解-非公平锁 (3)

内部调用了sync.lock(),其实是调用了NonfairSync对象的lock方法,也就是下面的方法内容。

/** * 非公平模式锁 */ static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * 执行锁动作,先进行修改状态,如果锁被占用则进行请求申请锁,申请锁失败则将线程放到队列中 */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } // 继承自AQS的tryAcquire方法,尝试获取锁操作,这个方法会被AQS的acquire调用 protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }

我们看到lock方法首先先对state状态进行修改操作,如果锁没有被占用则获取锁,并设置当前线程独占锁资源,如果尝试获取锁失败了,则进行acqurie方法的调用,例子中第一个线程当尝试获取锁是内部state状态为0,进行修改操作的时候,发现锁并没有被占用,则获得锁,此时我们来看一下内部变化的情况,如下图所示:

图解AQS原理之ReentrantLock详解-非公平锁

此时只是将state的状态更新为1,表示锁已经被占用了,独占锁资源的线程是Thread0,也就是exclusiveOwnerThread的内容,头节点和尾节点都没有被初始化,当第二个线程尝试去获取锁的时候,发现锁已经被占用了,因为上一个线程并没有释放锁,所以第二线程直接获取锁时获取失败则进入到acquire方法中,这个方法是AbstractQueuedSynchronizer中的方法acquire,先来看一下具体的实现源码如下所示:

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

我个人理解acquire方法不间断的尝试获取锁,如果锁没有获取到则现将节点加入到队列中,并将当前线程设置为独占锁资源,也就是独占了锁的意思,别的线程不能拥有锁,然后如果当前节点的前节点是头节点话,再去尝试争抢锁,则设置当前节点为头节点,并将原头节点的下一个节点设置为null,帮助GC回收它,如果不是头节点或争抢锁不成功,则会现将前面节点的状态设置直到设置为SIGNAL为止,代表下面有节点被等待了等待上一个线程发来的信号,然后就挂起当前线程。

我们接下来慢慢一步一步的分析,我们先来看一下NonfairSync中的tryAcquire,如下所示:

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

它调用的是他的父类方法,也就是ReentrantLock下Sync中的nonfairTryAcquire方法,这个方法主要就是去申请锁的操作,来看一下具体源码:

final boolean nonfairTryAcquire(int acquires) { //首先是一个被final修饰的方法 final Thread current = Thread.currentThread(); //获取当前线程 int c = getState(); //获取state的状态值 if (c == 0) { //如果状态等于0代表线程没有被占用 if (compareAndSetState(0, acquires)) { //cas修改state值 setExclusiveOwnerThread(current); //设置当前线程为独占模式 return true; } } else if (current == getExclusiveOwnerThread()) {//如果state状态不等于0则先判断是否是当前线程占用锁,如果是则进行下面的流程。 int nextc = c + acquires; //这个地方就说明重入锁的原理,如果拥有锁的是当前线程,则每次获取锁state值都会跟随递增 if (nextc < 0) // overflow //溢出了 throw new Error("Maximum lock count exceeded"); setState(nextc); //直接设置state值就可以不需要CAS return true; } return false; //都不是就返回false }

通过源码我们可以看到其实他是有三种操作逻辑:

如果state为0,则代表锁没有被占用,尝试去修改state状态,并且将当前线程设置为独占锁资源,表示获得锁成功

如果state大于0并且拥有锁的线程和当前申请锁的线程一致,则代表重入了锁,state值会进行递增,表示获得锁成功

如果state大于0并且拥有锁的线程和当前申请锁的线程不一致则直接返回false,代表申请锁失败

当第二个线程去争抢锁的时候,state值已经设置为1了也就是已经被第一个线程占用了锁,所以这里它会返回false,而通过acquire方法内容可以看到if语句中是!tryAcquire(arg),也就是!false=ture,它会进行acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法,这个方法里面又有一个addWaiter方法,从方法语义上能看到是添加等待队列的操作,方法的参数代表的是模式,Node.EXCLUSIVE表示的是在独占模式下等待,我们先来看一下addWaiter里面是如何进行操作,如下所示:

private Node addWaiter(Node mode) { //首先生成当前线程拥有的节点 Node node = new Node(Thread.currentThread(), mode); // 下面的内容是尝试快速进行插入末尾的操作,在没有其他线程同时操作的情况 Node pred = tail; //获取尾节点 if (pred != null) { //尾节点不为空,代表队列不为空 node.prev = pred; //尾节点设置为当前节点的前节点 if (compareAndSetTail(pred, node)) {//修改尾节点为当前节点 pred.next = node; //原尾节点的下一个节点设置为当前节点 return node; //返回node节点 } } enq(node); //如果前面入队失败,这里进行循环入队操作,直到入队成功 return node; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyffxz.html