pure storage新鲜电面面经

还有老题Event/Fire多线程。详细题目解释可以参考:

大意就是一个timeline上,event里面有两个method, 一个是register一个是fire,然后不停有Callback cb进来,register就一直把cb存起来,但是当call了fire这个method,先把之前所有register的event全部执行一遍,而且从此以后register不在存cb,而是直接运行当前传进来的cb
先来单线程

class CallBack {
        String name;
       
        public CallBack() {
        }
       
        public CallBack(String name) {
                this.name = name;
        }
       
        public void call() {
                System.out.println("CallBack Event " + this.name + "is running now");
        }
}

不用担心callback class,他给了这个class,我只是为了自己测试。看下面就好了

public class EventFire {
        Queue<CallBack> eventQueue = new LinkedList<>();
        boolean isFired = false;
       
        public void reg_cb(CallBack cb) {
                if (!isFired) {
                        eventQueue.offer(cb);
                }
                else {
                        cb.call();
                }
        }
       
        public void fire() {
                while (!eventQueue.isEmpty()) {
                        eventQueue.poll().call();
                }
                       
                isFired = true;
        }

}

然后继续,实现thread safe,先问了把isFired = true放在前面有什么好处。然后就是第二个版本

public class EventFire {
        Queue<CallBack> eventQueue = new LinkedList<>();
        boolean isFired = false;
        Lock lock;
       
        public void reg_cb(CallBack cb) {
                lock.lock();
                if (!isFired) {
                        eventQueue.offer(cb);
                        lock.unlock();
                }
                else {
                        lock.unlock();
                        cb.call();
                }
        }
       
        public void fire() {
                lock.lock();
                isFired = true;
                while (!eventQueue.isEmpty()) {
                        CallBack cb = eventQueue.poll();
                        lock.unlock();
                        cb.call();
                        lock.lock();
                }
                lock.unlock();
        }
}

然后walk through了一遍code和所有的锁,问了每一种情况(真的是每一种,加和不加有什么区别,有什么风险,千万不要紧张,一紧张就直接晕了)
然后ok,开始把锅扔给intership, 说现在有一个intership想优化code,写了第三个版本:

public class EventFire {
        Queue<CallBack> eventQueue = new LinkedList<>();
        boolean isFired = false;
        Lock lock;
       
        public void reg_cb(CallBack cb) {
                lock.lock();
                if (!isFired) {
                        eventQueue.offer(cb);
                        lock.unlock();
                }
                else {
                        lock.unlock();
                        cb.call();
                }
        }
       
        public void fire() {
                lock.lock();
                isFired = true;
                lock.unlock();
                while (!eventQueue.isEmpty()) {
                        CallBack cb = eventQueue.poll();
                        cb.call();
                }
        }
}

有什么区别,有没有风险,然后walk through了一遍,然后这个intership还改, 第四个版本:

public class EventFire {
        Queue<CallBack> eventQueue = new LinkedList<>();
        boolean isFired = false;
        Lock lock;
       
        public void reg_cb(CallBack cb) {
                lock.lock();
                if (!isFired) {
                        eventQueue.offer(cb);
                        lock.unlock();
                }
                else {
                        lock.unlock();
                        cb.call();
                }
        }
       
        public void fire() {
                lock.lock();
                lock.unlock();
                isFired = true;
                while (!eventQueue.isEmpty()) {
                        CallBack cb = eventQueue.poll();
                        cb.call();
                }
        }
}

问有没有风险,当然有风险,锁和没锁一样的。然后继续改,第五个版本:

public class EventFire {
        Queue<CallBack> eventQueue = new LinkedList<>();
        boolean isFired = false;
        Lock lock;
       
        public void reg_cb(CallBack cb) {
                lock.lock();
                if (!isFired) {
                        eventQueue.offer(cb);
                        lock.unlock();
                }
                else {
                        lock.unlock();
                        cb.call();
                }
        }
       
        public void fire() {
                isFired = true;
                lock.lock();
                lock.unlock();
                while (!eventQueue.isEmpty()) {
                        CallBack cb = eventQueue.poll();
                        cb.call();
                }
        }
}

问有没有风险,这个版本没有风险,这是一个barrier,可以在改完了isFired之后去尝试aquire lock,没拿到的话就会进入wait,所以这种改法是对的。这里开始没答好,我紧张了满脑子就记得lock是lock整个instance,然后完全没考虑如果没有去aquire lock,其实thread是不会wait的。比如如果fire method里面没有lock的话,他是可以在register被lock了的情况下还能执行的
然后聊了聊如果要求keep order有什么想法,用两个queue,然后交换queue的时候lock,他说you are on a good track
然后聊了聊不加锁实现thread safe,主要思路就是在register,在判断了isFired之后进去的时候判断queue为不为空,可以知道其他thread有没有call fired,并且有没有执行完。
然后就问有没有什么问题,就闲聊了一会,然后说时间超了,就说88了

强烈希望攒人品,求过到下一轮,攒人品攒人品攒人品

lz很棒啊 = =。
我看了评论突然发现,如果要去保持顺序,把flag放在后面不就行了么 这和用两个queue原理是一样么:)
如果有错误 欢迎指正~~
1个queue。flag放在最后。 这样fire里面的queue不停地push进来callback 从而保证顺序
2 个queue。 q1,q2。在 register 如果flag = true ,我们再判断fire() (应该是看q1是否为空)是否结束,没结束把callback就存入q2.。 fire里判断 q2是否为空,为空缓存的cb就call完了,就函数结束。不然空就交换q1 q2。 继续循环

callback的run的时间你是不知道的,很有可能出现的情况是你的while loop刚好运行完,然后还没来得及改变flag,另外一个thread这时候拿到lock,判断flag == false,然后进去往q里加一个event,然后release lock,然后第一个thread拿到lock,改变flag == true。

你的那个event就永远都清不了了

楼主求问最后的follow up哪里 拿两个queue为什么可以keep order吖 这个keep order是指要按入队的顺序执行?

对对,多用一个queue相当于是缓存,可以保证入队的顺序和出队是一样的