V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
amiwrong123
V2EX  ›  Java

为啥 AQS 的 CondtionObject 的 firstWaiter 不需要是 volatile 的?

  •  
  •   amiwrong123 · 2020-06-09 23:51:21 +08:00 via Android · 1605 次点击
    这是一个创建于 1409 天前的主题,其中的信息可能已经有所发展或是发生改变。

    其实关于这一点,网上大部分人都会说,要 await()必然要先 lock(),既然 lock()了就表示没有竞争,没有竞争自然也没必要使用 volatile+CAS 的机制去保证什么。所以 firstWaiter 不需要是 volatile 的。

    但是实际上我感觉还是会有问题,比如你执行下面这段程序:

            final Lock lock=new ReentrantLock();
            Condition Emptycondition = lock.newCondition();
            Emptycondition.await();  //这样会抛出异常
    

    比如你让一个子线程去做上面的事,会抛出异常,因为 await()的时候没有获取锁呢。但是在抛出异常之前,你已经执行了一部分代码了:

            public final void await() throws InterruptedException {
                if (Thread.interrupted())
                    throw new InterruptedException();
                Node node = addConditionWaiter();
                int savedState = fullyRelease(node);//在这里抛出的异常,意味着已经执行了 addConditionWaiter
                int interruptMode = 0;
                while (!isOnSyncQueue(node)) {
                    LockSupport.park(this);
                    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                        break;
                }
                if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                    interruptMode = REINTERRUPT;
                if (node.nextWaiter != null) // clean up if cancelled
                    unlinkCancelledWaiters();
                if (interruptMode != 0)
                    reportInterruptAfterWait(interruptMode);
            }
    
        final int fullyRelease(Node node) {
            boolean failed = true;
            try {
                int savedState = getState();
                if (release(savedState)) {//具体的说,是这里抛出异常了
                    failed = false;
                    return savedState;
                } else {
                    throw new IllegalMonitorStateException();
                }
            } finally {
                if (failed)//抛出异常后,failed 为真,但善后处理只是将 node 的状态变成 CANCELLED
                    node.waitStatus = Node.CANCELLED;
            }
        }
    

    再来看 addConditionWaiter 的逻辑:

            private Node addConditionWaiter() {
                Node t = lastWaiter;
                // If lastWaiter is cancelled, clean out.
                if (t != null && t.waitStatus != Node.CONDITION) {
                    unlinkCancelledWaiters();
                    t = lastWaiter;
                }
                     Node node = new Node(Thread.currentThread(), Node.CONDITION);
                if (t == null)
                    firstWaiter = node;//对不保证可见性的变量进行赋值了
                else
                    t.nextWaiter = node;
                lastWaiter = node;//对不保证可见性的变量进行赋值了
                return node;
            }
    

    好了,现在执行完我的那三行测试代码后,Emptycondition 的条件队列上有了一个 node,而且这个 node 包装了一个死掉的线程。因为抛出异常

    现在假设有另一个线程执行正常流程的代码:

            final Lock lock=new ReentrantLock();
            Condition Emptycondition = lock.newCondition();
            lock.lock();
            try{
                Emptycondition.await();  
            } finally {
                lock.unlock();
            }
            
    

    现在条件是:

    1. 线程 A 执行 测试代码,会执行到 addConditionWaiter
    2. 线程 B 执行 正常流程代码,也会执行到 addConditionWaiter
    3. 两个线程可能同时执行 addConditionWaiter,却没有对 firstWaiter 进行 volatile+CAS 的保护,难道这样不会有问题吗?
    12 条回复    2020-07-18 23:27:51 +08:00
    sagaxu
        1
    sagaxu  
       2020-06-10 00:19:53 +08:00 via Android
    因为你的代码没有做到要 await()必然要先 lock()
    amiwrong123
        2
    amiwrong123  
    OP
       2020-06-10 00:30:23 +08:00 via Android
    @sagaxu
    额,我好像没懂你意思。所以你想说 firstWaiter 不加 volatile,就会有问题呗
    muyunn
        3
    muyunn  
       2020-06-10 01:10:17 +08:00 via iPhone
    好长时间不用,差点被楼主绕进去,你必须先调用 lock.lock()获取锁,然后才可以 await
    vk42
        4
    vk42  
       2020-06-10 01:16:50 +08:00
    lz 是想说不先 lock 的情况下后果不可控?但本来没按规则先 lock 不就是 bug 吗?直接修 bug 不是更简单么
    luckyrayyy
        5
    luckyrayyy  
       2020-06-10 02:41:50 +08:00 via iPad
    我的哥啊,你要研究一年 aqs ?
    amiwrong123
        6
    amiwrong123  
    OP
       2020-06-10 08:16:43 +08:00 via Android
    @muyunn
    是的,标准用法是这样
    amiwrong123
        7
    amiwrong123  
    OP
       2020-06-10 08:20:45 +08:00 via Android
    @vk42
    好吧,所以相当于决定权给了用户,作者默认使用者是按照正确用法使用的
    amiwrong123
        8
    amiwrong123  
    OP
       2020-06-10 08:40:25 +08:00 via Android
    @luckyrayyy
    哈哈哈,最近发帖太多被发现了吗。这几天 condition 的实现看完,就不看 aqs 了
    yamasa
        9
    yamasa  
       2020-06-10 15:37:10 +08:00
    佩服这牛角尖精神。虽然我觉得现行环境下死磕 jdk 源码不如刷 leetcode 来的现实。
    amiwrong123
        10
    amiwrong123  
    OP
       2020-06-10 16:30:28 +08:00
    @yamasa
    哎,所以我是又努力错方向了吗 ==
    lux182
        11
    lux182  
       2020-06-10 20:35:59 +08:00
    我觉得如果你用最简洁的语言表述清楚了,可能你也就找到答案了
    amiwrong123
        12
    amiwrong123  
    OP
       2020-07-18 23:27:51 +08:00
    给自己回复一下,我问的这个问题,其实是个 bug,详见 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8187408
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5519 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 01:40 · PVG 09:40 · LAX 18:40 · JFK 21:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.