V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
Newyorkcity
V2EX  ›  问与答

这段生产者消费者的代码在多线程中可能存在啥问题?

  •  1
     
  •   Newyorkcity · 2020-08-29 17:29:35 +08:00 · 1810 次点击
    这是一个创建于 1307 天前的主题,其中的信息可能已经有所发展或是发生改变。
        public static class Stack {
    
            private final LinkedList<Integer> queue = new LinkedList<>();
    
            public synchronized Integer pop() throws InterruptedException {
                if (queue.size() <= 0) {
                    wait();
                }
                return queue.removeLast();
            }
    
            public synchronized void push(Integer integer) {
                queue.addFirst(integer);
                notify();
            }
        }
    
    

    面试题..题目说这个栈在多线程中可能出现问题,请指出出现问题的情景...我想了半天也没想出来什么情景下回出问题,还请指教..

    谢谢

    18 条回复    2020-08-30 11:30:39 +08:00
    Jooooooooo
        1
    Jooooooooo  
       2020-08-29 17:38:30 +08:00
    pop 和 push 是可以并发的啊

    线程 1 判断 if(queue.size() <= 0), 成功

    线程 2 执行 push 并且 notify

    线程 1 执行 wait
    Newyorkcity
        2
    Newyorkcity  
    OP
       2020-08-29 18:31:24 +08:00
    @Jooooooooo 额?你是说限制得过分了?
    Jooooooooo
        3
    Jooooooooo  
       2020-08-29 18:34:26 +08:00
    @Newyorkcity 线程 1 执行 wait 之后程序就死在那了
    Newyorkcity
        4
    Newyorkcity  
    OP
       2020-08-29 18:38:37 +08:00
    @Jooooooooo 线程 1 判断 if(queue.size() <= 0) 成功,此时还没有执行 wait,线程 1 持有锁,线程 2 无法执行 push 方法的吧?
    Jooooooooo
        5
    Jooooooooo  
       2020-08-29 19:05:07 +08:00
    @Newyorkcity 记错了

    这个我想了一下, 应该是 synchronized 只能作用在 stack 上而无法作用在 queue 上, 导致 queue 内部的行为无法遵守 happen before 原则.
    iseki
        6
    iseki  
       2020-08-29 19:19:46 +08:00 via Android
    不是吧…synchronized 是锁 this 的我如果没记错,那么从并发安全上看应该没问题?
    iseki
        7
    iseki  
       2020-08-29 19:20:04 +08:00 via Android
    jimages
        8
    jimages  
       2020-08-29 19:28:20 +08:00 via iPhone
    死锁,当 size 为 0 的时候,在里面 wait,但是此时已经拿了锁了。push 没有拿到锁,无法 push 。所以一个已经持有锁的线程在 wait,没有持有锁的线程想 push 得等锁,形成死锁。
    Newyorkcity
        9
    Newyorkcity  
    OP
       2020-08-29 19:31:33 +08:00
    @jimages 额。。。wait 函数一旦执行,线程转入等待状态并会释放由于 synchronized 拿到的对象锁,所以生产者线程有机会生产出产品的吧。。
    jimages
        10
    jimages  
       2020-08-29 19:43:40 +08:00 via iPhone
    @Newyorkcity 哦哦哦,我看错了😂。那应该是两个 pop 线程一个 push 线程的问题,就比如一个在 wait 被卡住,一个在访问 pop 的时候被卡住。如果比如现在 push 了一个,然后 notify 。现在有两个线程需要锁,一个是 wait 那需要一个锁,一个是新进入的 pop 需要锁,假如是新来的那个 pop 拿到了锁,pop 出最后一个元素。然后是 wait 那个线程拿到元素。由于是 if 不是一个 while 判断。所以此时 pop 出一个,但此时 queue 已经没有元素了。
    LinJunzhu
        11
    LinJunzhu  
       2020-08-29 19:55:52 +08:00   ❤️ 1
    @jimages synchronized 的 monitor 中,线程 A 调用 notify() 后,默认策略是从 WaitSet 队列内拿出被堵塞的线程,插入 EntrySet 队列头,随后线程 A 执行完毕,便会去 EntrySet 拿出堵塞的线程来唤醒执行,所以不存在你说的被 [新来的 pop 线程] 拿到了锁
    MoHen9
        12
    MoHen9  
       2020-08-29 21:15:39 +08:00 via Android
    可能是执行效率低吧,你这是实现了个阻塞队列,面试官可能认为直接使用阻塞队列会更好。
    leafre
        13
    leafre  
       2020-08-29 21:41:25 +08:00
    我觉得没问题,有大佬找出问题 @我下
    LinJunzhu
        14
    LinJunzhu  
       2020-08-30 00:57:13 +08:00   ❤️ 1
    重新看了下 Monitor 的源码,更正一下,是会存在 @jimages 所说的, 当持有锁的线程 A(push 方法)调用 notify()后 并退出同步代码块时,会释放持有的锁,此时唤醒的堵塞线程 B (在 wait() 处的线程)重新争抢锁,是有可能会被新来的线程 C 调用 pop() 抢到锁的,此时 C 执行完毕后,锁释放,若恰好轮到线程 B 获得了锁,此时队列已经空了,不满足条件,执行则会报错,因此应该修改为:

    while (queue.size() <= 0) {
    wait();
    }

    在该处进行死循环判断。
    776491381
        15
    776491381  
       2020-08-30 09:56:54 +08:00 via Android
    使用 notifyAll,不要使用 notify,可能会陷入死锁,具体可以自己分析一下多线程调用流程以及 notify 原理
    776491381
        16
    776491381  
       2020-08-30 09:57:27 +08:00 via Android
    同时需要使用 while 循环判断
    Newyorkcity
        17
    Newyorkcity  
    OP
       2020-08-30 10:28:51 +08:00
    @776491381 额..就是想过了没想出所以然来....请具体说说?
    falsemask
        18
    falsemask  
       2020-08-30 11:30:39 +08:00   ❤️ 1
    线程会被虚假唤醒,if 要改成 while
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5874 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 44ms · UTC 02:16 · PVG 10:16 · LAX 19:16 · JFK 22:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.