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

2020-08-29 17:29:35 +08:00
 Newyorkcity
    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();
        }
    }

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

谢谢

1995 次点击
所在节点    问与答
18 条回复
Jooooooooo
2020-08-29 17:38:30 +08:00
pop 和 push 是可以并发的啊

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

线程 2 执行 push 并且 notify

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

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

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

在该处进行死循环判断。
776491381
2020-08-30 09:56:54 +08:00
使用 notifyAll,不要使用 notify,可能会陷入死锁,具体可以自己分析一下多线程调用流程以及 notify 原理
776491381
2020-08-30 09:57:27 +08:00
同时需要使用 while 循环判断
Newyorkcity
2020-08-30 10:28:51 +08:00
@776491381 额..就是想过了没想出所以然来....请具体说说?
falsemask
2020-08-30 11:30:39 +08:00
线程会被虚假唤醒,if 要改成 while

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/702437

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX