京东背调挂了,月薪40K的offer飞了熬过了五轮技术面,终于拿下京东40K的offer,却倒在了背调环节,京东的背调,从来不只是走个过场。
刚看到个贴子,说有人熬过五轮技术面,好不容易拿到京东40K 的 offer,结果最后背调挂了,offer 当场飞走。
网友们吵成一片,有骂京东“太狠”的,也有说“活该,谁让你简历不老实”的,还有人感慨:现在找工作比谈恋爱还难解约。
我觉得这事吧,先别急着骂平台。大厂的背调,本来就不是走过场,人家给你一个月 40K,不可能只听你自己怎么说。你过去的离职原因、履历真假、有没有严重的职业黑点,这些本来就在对方的“风险清单”里。
但反过来说,企业也得把规则说清楚:背调做到什么程度,哪些问题是红线,最好在发 offer 前就沟通到位,别让候选人一路辛辛苦苦走到最后,才被一个模糊的“背调不通过”打回原形,这种体验确实挺糟心。
面试题:哲学家进餐
昨天晚上十一点多,我还在公司加班写代码,楼下食堂早就关门了,只能点了个外卖泡面。我们组那个小李端着泡面过来,一边吃一边问我:哥,那个什么…哲学家进餐,到底在讲啥啊?为啥面试老爱问这个鬼东西?
我当时脑子都困糊了,不过一听算法题,还是精神一下,就跟他边吃边聊了半天。
你想象一下,圆桌上坐了 5 个程序员,哦不,哲学家。桌子上有 5 根筷子,每个人左手边一根、右手边一根。
他们的日常就两件事: 要么思考人生(think),要么吃面(eat)。 吃面的时候必须要两根筷子都拿到才能吃。
如果你很直觉地写代码:每个哲学家线程先拿左边筷子,再拿右边筷子,看起来很合理对吧?结果就是——五个人一起伸手,都拿到自己左边那根,然后一起在那儿干等右边那根,谁也凑不齐两根,整个系统就僵住了,这就是典型死锁。
再极端一点,有的同学写了一个“完美锁方案”,结果发现:有的哲学家总是运气不好,老被别人抢先一步,永远吃不上,这叫饥饿(starvation)。听着就挺惨。
其实面试官不是想看你会不会“哲学”,就是想看三件事:
你能不能发现这里有死锁风险 你能不能用一点规矩把这个死锁拆掉 你写并发代码的时候,会不会考虑公平、饥饿这些细节
换到工作场景里,就是:一堆线程抢一堆资源,你设计得不好,线上就给你来一波“全部卡死,系统挂掉”。
我那会儿就给小李写了个最常用、也比较好讲清楚的版本:给筷子加锁 + 规定拿筷子的顺序。
核心思路就一句话:任何人拿筷子的时候,永远先拿编号小的那根,再拿大的那根。
这样就不会出现“我拿你,你拿我,大家互相等”的循环等待了,环给你掰断了,自然就不死锁。
代码我给他敲了个简化版,大概长这样:
import java.util.concurrent.locks.ReentrantLock;
classChopstick{
privatefinalint id;
privatefinal ReentrantLock lock = new ReentrantLock();
publicChopstick(int id){
this.id = id;
}
publicintgetId(){
return id;
}
publicvoidpickUp(){
lock.lock();
}
publicvoidputDown(){
lock.unlock();
}
}
classPhilosopherimplementsRunnable{
privatefinalint id;
privatefinal Chopstick left;
privatefinal Chopstick right;
publicPhilosopher(int id, Chopstick left, Chopstick right){
this.id = id;
this.left = left;
this.right = right;
}
@Override
publicvoidrun(){
try {
while (true) {
think();
eat();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
privatevoidthink()throws InterruptedException {
// 随便睡一下,假装在思考
Thread.sleep(200 + (long)(Math.random() * 300));
}
privatevoideat()throws InterruptedException {
// 关键点:永远先拿编号小的那根筷子
Chopstick first = left;
Chopstick second = right;
if (left.getId() > right.getId()) {
first = right;
second = left;
}
first.pickUp();
second.pickUp();
try {
System.out.println("哲学家 " + id + " 正在吃面...");
Thread.sleep(200 + (long)(Math.random() * 300));
} finally {
second.putDown();
first.putDown();
}
}
}
publicclassDiningPhilosophersDemo{
publicstaticvoidmain(String[] args){
int n = 5;
Chopstick[] chopsticks = new Chopstick[n];
for (int i = 0; i < n; i++) {
chopsticks[i] = new Chopstick(i);
}
for (int i = 0; i < n; i++) {
Chopstick left = chopsticks[i];
Chopstick right = chopsticks[(i + 1) % n];
Philosopher p = new Philosopher(i, left, right);
new Thread(p, "Philosopher-" + i).start();
}
}
}
小李看完以后就问:为啥这样就不给死锁了?我蹲在工位旁边给他比划了半天:
所有筷子有一个全局的顺序:0、1、2、3、4 不管哪个哲学家,拿筷子都按“小号先、大号后” 所以不可能出现 A 拿了 1 等 2,B 拿了 2 等 1 这种互相环形等待 你可以脑补一下:每个人的锁请求顺序都是“从小到大”一路往上走,没有人往回走,自然就不会绕成圈
这个就是传说中的“破坏循环等待条件”,属于最经典的一个反死锁套路,放到别的资源竞争场景里也很好用,比如多个锁、多张表一起加锁那种。
这个版本里,因为大家都是 while(true) 这么干,严格来说还是有可能有人运气差、总是慢一步。但实际运行里,线程调度比较随机,大家大概率都能吃上。
如果你真要对“公平”较真,可以玩得再细一点,比如:
限制每个哲学家连续吃饭次数 用一个“服务生线程”控制最多只允许 4 个人同时拿筷子 或者用 Semaphore做限流,谁吃完了再放回 permit
这些就有点超出面试常规问法了,一般你把:问题讲清楚 + 死锁原因说清楚 + 像上面这样写个有顺序的锁方案基本就够用了。
我跟小李聊到最后,人已经困得不行了,他突然来一句: “哥,那你现实生活吃饭会不会先给筷子编号,再顺序拿?” 我说:我现实生活只会先看公司报不报销,别的以后再哲学吧……
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html