ReentrantLock和AQS源码解读系列三

lock

这个方法是在获取锁的过程中不会响应中断,也不会退出,要么阻塞,要么继续获取锁,直到成功获取了才,如果期间有中断则会在最后进行一次中断。

lockInterruptibly

这个方法刚好和上面相反,只要有中断,阻塞了就会马上响应,终止获取锁的过程。稍微来看下他的源码吧:
在这里插入图片描述
如果开始就有中断过了,就直接抛出去了:
在这里插入图片描述
直接抛出异常,捕获后设置取消状态继续抛出:
在这里插入图片描述
其实前面即篇看过之后,再看这个就很简单了,如果阻塞了,然后有中断,就醒来直接抛出中断异常了,然后业务代码那里就捕获处理就可以了:
在这里插入图片描述
不过这种方式是被动的,是其他线程来打断他,下面看看主动自己打断。

tryLock

直接尝试一次获取锁,失败就算了,仅仅只是一次尝试,插队:
在这里插入图片描述

tryLock(long timeout, TimeUnit unit)

有超时时间的获取锁,可以被打断:
在这里插入图片描述
跟可被打断的lockInterruptibly内部很像,
在这里插入图片描述
然后是doAcquireNanos

 private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        final long deadline = System.nanoTime() + nanosTimeout;//设置到期时间
        final Node node = addWaiter(Node.EXCLUSIVE);//排队
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();//查看剩余时间
                if (nanosTimeout <= 0L) {//时间到了
                    cancelAcquire(node);//设置取消状态
                    return false;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > SPIN_FOR_TIMEOUT_THRESHOLD)//大于1微秒的时候开始定时阻塞,小于1微秒就不阻塞了,太小就可能就不精确了,这样还可以给更多机会获取锁
                    LockSupport.parkNanos(this, nanosTimeout);//时间到了之后唤醒
                if (Thread.interrupted())//如果有中断过了,就抛出
                    throw new InterruptedException();
            }
        } catch (Throwable t) {
            cancelAcquire(node);//取消后继续抛出
            throw t;
        }
    }

Condition同步

其实这个就类似于同步中的wait和notify,只是更加细粒度的控制,而且可以中断。
其实ReentrantLock可以创建很多个条件,每个条件都会有一个队列,不满足条件的时候等待,满足条件后被其他线程唤醒。具体我们来分下下吧,我们以单个条件为例,写了个简单的例子,一个线程等待有钱的条件,另外一个线程给钱,拿到钱后就可以用,当然也可能中间被中断了:

public class ConditionLockTest {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition moneyCondition = lock.newCondition();
    private static boolean hasMoney;


    public static void main(String[] args) throws InterruptedException {
        Thread t1=null;
       for (int i = 0; i < 1; i++) {
             t1 = new Thread(() -> {
                lock.lock();
                try {
                     while (!hasMoney) {
                        System.out.println(Thread.currentThread() + "请求条件");
                        moneyCondition.await();

                    }
                    if (!Thread.currentThread().isInterrupted()) {
                        if (hasMoney) {
                            System.out.println(Thread.currentThread() + "满足条件,开始买东西");
                        } else {
                            System.out.println(Thread.currentThread() + "没法买东西");
                        }
                    }else {
                        System.out.println(Thread.currentThread() + "被打断了");
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread() + "抛出异常");
                } finally {
                    lock.unlock();
                }
            }, "花钱线程"+i);
            t1.start();
        }
        //Thread.sleep(500);
        //t1.interrupt();
        Thread.sleep(1000);

        new Thread(() -> {
            lock.lock();
            try {
                hasMoney = true;
                System.out.println("给你钱");
                moneyCondition.signal();
            } finally {
                lock.unlock();
            }
        }, "给钱线程").start();
    }
}

我们用ReentrantLock创建了moneyCondition,然后在一个线程中获取锁,然后等待,另外一个线程一秒后获取锁通知,这里要注意signal仅仅只是通知条件等待队列的头部结点,所以可能多线程的时候是不确定哪个线程排头部的,所以说是随机通知的,一般都是使用signalAll,我这里就一个线程当然没问题,如果是多个,那其他线程还是会等待。原理就那么简单,就是阻塞等待满足条件被唤醒。其实这里隐含着一个流程,就是先获取锁,然后等待,释放锁,最后又获取锁,执行业务,释放锁。

await等待

其实这些东西源码里都有体现的,接下来我们看看源码吧,获取锁的那些前面文章都讲过了,不讲了,直接看await

    public final void await() throws InterruptedException {
            if (Thread.interrupted())//有中断就抛出异常
                throw new InterruptedException();
            Node node = addConditionWaiter();//增加一个条件等待结点,这个条件队列是单列表的
            int savedState = fullyRelease(node);//释放当前锁
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);//不在同步队列里就阻塞,在同步队列就说明被通知了,被中断是不会在同步队列的
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//被中断就出来了
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//获得到了锁,是通知之后的中断,设置为REINTERRUPT
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled 清除一下后面的取消状态
                unlinkCancelledWaiters();
            if (interruptMode != 0)//被中断过就要报告中断
                reportInterruptAfterWait(interruptMode);
        }

这里主要大致流程就是加入到条件等待结点,然后释放锁,之后判断是否在同步队列中,不在就阻塞,直到被在同步队列了或者被中断后继续,如果是被唤醒的就会去进行获取锁,然后处理一些异常类型,释放取消的条件等待结点,最后返回继续做业务,然后释放锁。具体细节我们一个个来看吧。

addConditionWaiter

其实就是增加了一个条件等待结点,顺便检查下有没取消状态,有就清理一下。

 private Node addConditionWaiter() {
            if (!isHeldExclusively())//如果当前线程没有占有锁,就不能调用这个了,必须是获得锁后才能加条件
                throw new IllegalMonitorStateException();
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.如果最后一个条件等待结点存在,且状态不是CONDITION,就清理一遍等待队列
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //创建新结点,状态就是CONDITION,其实就CONDITION和CANCELLED两种状态
            Node node = new Node(Node.CONDITION);

            if (t == null)//如果队列是空的,node就是第一个结点,头结点指向它
                firstWaiter = node;
            else
                t.nextWaiter = node;//否则node就是最后一个结点
            lastWaiter = node;//尾结点指向它
            return node;
        }
unlinkCancelledWaiters

这个就是清理队列,把取消的断链,其实就是一个单项链表的删除结点的操作:

 private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;//总是落后t,这样才能在t的有问题的时候,可以指向next没问题的结点
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;//更改第一个结点的位置
                    else
                        trail.nextWaiter = next;//越过不是CONDITION结点,指向下一个越过不是CONDITION的结点结点
                    if (next == null)
                        lastWaiter = trail;//如果发现最后一个结点不是CONDITION结点,那就把trail作为最后的结点
                }
                else
                    trail = t;//不是CONDITION结点就指向t
                t = next;//t继续指向后继结点
            }
        }

简单来说就是这样的:
在这里插入图片描述
然后把新的条件等待结点加入条件等待队列:
在这里插入图片描述

fullyRelease

释放锁,里面其实就是前面讲过的释放锁release,只是这里还要把锁的状态保存下来,如果释放失败就会抛出非法异常,条件等待结点状态被设置为取消:

   final int fullyRelease(Node node) {
        try {
            int savedState = getState();
            if (release(savedState))//释放锁
                return savedState;
            throw new IllegalMonitorStateException();//否则抛出异常,说明没有锁的拥有权
        } catch (Throwable t) {
            node.waitStatus = Node.CANCELLED;
            throw t;
        }
    }

isOnSyncQueue

接着就是判断等待结点是否在队列中,如果不在,就要阻塞,等待被唤醒或者中断,先看看这个方法:

final boolean isOnSyncQueue(Node node) {//判断是否在同步队列当中
        if (node.waitStatus == Node.CONDITION || node.prev == null)//如果是CONDITION状态,或者没有前驱的结点,肯定不同步队列当中,同步队列当中的肯定是有前驱的,而且不会是CONDITION状态
            return false;
        if (node.next != null) // If has successor, it must be on queue有后继结点,肯定在同步队列中,条件等待队列是没有next,只有nextWaiter
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        return findNodeFromTail(node);//如果node.prev不为空,可能是还没CAS,还没在同步队列中
    }

为什么最后还有findNodeFromTail判断,因为node.prev不为空的情况有两种:
第一,已经在同步队列中,CAS执行成功。
第二,没在同步队列中,因为可能还没CAS,或者CAS失败了,只有CAS成功了才算在同步队列里:
在这里插入图片描述
也就是这种情况,前驱有,还没CAS或者CAS失败的时候,但是没在同步队列里,这个细节要注意:
在这里插入图片描述

findNodeFromTail

所以这里还得要从尾到头遍历下,看看在不在里面,这个比较简单,就是比较对象:
在这里插入图片描述
这也就说明了,同步队列里的结点对象和条件等待队列里的等待结点是同一个,只是内部的属性不一样。我们思考个问题,我们明明是新创建的一个结点到条件等待队列呀,怎么就会在同步队列里,又没什么操作,其实是因为其他线程调用了notify/notifyAll,他会把条件等待队列的结点加到同步队列的队尾,可以是一个结点notify,也可以是所有等待的结点notifyAll

checkInterruptWhileWaiting

如果被唤醒或者中断,就会判断是否有中断,有中断就终止循环了,要知道中断类型是什么,以便于后面的处理:

  private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?//如果中断,是在通知前中断就THROW_IE抛出异常,否则就是REINTERRUPT
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
final boolean transferAfterCancelledWait(Node node) {
        if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {//如果状态可以改,说明还在条件等待队列里,没有被signal通知,就进同步队列
            enq(node);
            return true;//中断在通知前,也就是要抛出中断异常
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin. signal通知后中断,要等待进入同步队列
         */
        while (!isOnSyncQueue(node))//如果不在同步队列中的,就让出CPU,不过不一定会马上让出,直到进去队列位置,也就是被signal通知了
            Thread.yield();
        return false;
    }

中断的处理

 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//获得到了锁,是通知之后的中断,设置为REINTERRUPT
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled 清除一下后面的取消状态
                unlinkCancelledWaiters();
            if (interruptMode != 0)//被中断过就要报告中断
                reportInterruptAfterWait(interruptMode);

如果获得锁又被中断过,且中断是通知后发生的,设置中断模式为REINTERRUPT,然后再次清除取消状态的条件等待结点,最后有中断状态报告中断:

   private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)//通知前的中断,要抛出中断异常
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)//通知后的中断,标记一下就好
                selfInterrupt();
        }

其实这个刚开始也不太好理解,你可以自己做实验,比如这样模拟通知前中断:
在这里插入图片描述
结果就是-1,也就是通知前被中断:
在这里插入图片描述
然后模拟通知后被中断:
在这里插入图片描述
这个可能要试好多次,不一定就是在通知后执行,但是只要是在开始执行,结果就是1,也就是在通知后被中断:
在这里插入图片描述

至此,条件等待的流程梳理完了,然后我们继续看看通知流程是怎么样的。

signal通知

也就是唤醒条件等待结点,我们来看看:

    public final void signal() {
            if (!isHeldExclusively())//判断是否占有锁,不然报非法异常
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)//首等待结点存在才唤醒
                doSignal(first);
        }

doSignal

  private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)//将firstWaiter指向下一个,如果为null的话
                    lastWaiter = null;//后面没等待的了, lastWaiter设为null,
                first.nextWaiter = null;//断开nextWaiter
            } while (!transferForSignal(first) && //转移成功就不用继续了
                     (first = firstWaiter) != null);//转移不成功如果还有下一个等待结点,那就继续转移下一个
        }
transferForSignal

这个就是将条件等待结点转移到同步队列里:

    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
            return false;//如果不能CAS操作,表示状态是CANCELLED了

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);//入队,返回的是前驱结点
        int ws = p.waitStatus;
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))//如果前驱结点状态是CANCELLED或者状态不能设置为SIGNAL就唤醒结点
            LockSupport.unpark(node.thread);//前驱如果没有SIGNAL,那直接唤醒,反正阻塞前可以设置前驱状态
        return true;
    }

其实就是把条件等待队列的第一个结点转移到同步等待队列中,如果第一个不满足条件,那就转后面的,成功为止。从这里可以看出条件等待队列是公平的,先进先出。画个示意图比较好理解,开始的样子:
在这里插入图片描述
转换后的,我用虚线表示了有改变的地方,红色表示同步队列,绿色表示条件等待队列:
在这里插入图片描述
还有CAS失败的情况,黄色表示取消状态的:
在这里插入图片描述
至此,基本通知也分析好了,之后这个结点就在同步队列里,然后获取锁,然后去做事,最后释放锁,当然中间如果有中断的话就要处理。这里注意是中断和抛出异常是两种方式,业务方面也要捕捉处理。

signalAll通知

顺便把signalAll也一起讲了吧,这个一看就是把所有条件等待队列里的所有结点都加入到同步队列中,具体代码也比较好理解,就不多说了:

 public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }
 private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;//打断后继
                transferForSignal(first);//加入队列
                first = next;//继续下一个
            } while (first != null);//从头到尾都要
        }

总结

基本的条件同步也将完了,当然肯定还有一些细节不到位,需要继续研究,其实我觉得这个并发这块确实是需要花时间研究的,不是顺序执行下来就完事了,中间可能会有很多的变化,所以还要多看看,温故而知新。
还有一些方法比如await(long time, TimeUnit unit)awaitNanos(long nanosTimeout)这个就是跟tryLock(long timeout, TimeUnit unit)类似,而awaitUninterruptibly()也就是不可被中断的,只要把中断的那部分删除即可理解,所以就不多说了,有些类似的只要基本的理解了,都好理解。

其实条件同步就是把没符合条件的线程都关紧一个房间(条件等待队列),并且释放锁,然后条件符合的时候一个个放或者一起放出来,让他们去同步队列里,获得锁之后再进行业务处理。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值