不能加班的人,请离开我的团队。。
刚刚看到一个有意思的吐槽,领导要求不愿意加班的人直接离开,工作时间是从早9点到凌晨2点,尼玛,这是不把人当人呀
作为一名程序员,我真的是有点无语。把这种“加班文化”当成常态来要求,未免太过苛刻了。毕竟,写代码不是拼体力,而是拼脑力。
如果长期超负荷工作,脑力的消耗会大大降低效率,甚至可能导致程序质量下降,出问题反而得不偿失。而且咱就是说,你是真把劳动法当摆设呀。
算法题:交替打印 FooBar
今天咱们聊个我以前在公司面试经常会问的算法题——交替打印 FooBar,听着名字像个“套路题”,但别小看它,面试的时候真有人翻车过!尤其是Java这边搞多线程的童鞋,分分钟踩坑。
这个题目其实就是两个线程,一个打印 "foo",一个打印 "bar",最后输出成 "foobarfoobar..." 这样交替的格式。看起来很水,结果实现起来小心绕进去。
我先把问题代码说一下,Java这边的题目通常给你个模板,让你补全方法:
classFooBar{
privateint n;
publicFooBar(int n){
this.n = n;
}
publicvoidfoo(Runnable printFoo)throws InterruptedException {
// 打印 "foo"
}
publicvoidbar(Runnable printBar)throws InterruptedException {
// 打印 "bar"
}
}
然后你得让两个线程“你来我往”地去打印。这个题本质就是线程间同步的问题,处理不好不是死锁就是顺序错乱。
我之前写法是用 Semaphore,确实简单暴力,代码直接走起👇:
import java.util.concurrent.Semaphore;
classFooBar{
privateint n;
private Semaphore fooSem = new Semaphore(1); // 一开始允许foo执行
private Semaphore barSem = new Semaphore(0); // bar线程先阻塞
publicFooBar(int n){
this.n = n;
}
publicvoidfoo(Runnable printFoo)throws InterruptedException {
for (int i = 0; i < n; i++) {
fooSem.acquire(); // 获取许可,没拿到就等
printFoo.run();
barSem.release(); // 释放bar的许可
}
}
publicvoidbar(Runnable printBar)throws InterruptedException {
for (int i = 0; i < n; i++) {
barSem.acquire(); // 先卡住
printBar.run();
fooSem.release(); // 释放foo的许可
}
}
}
这个写法真的很丝滑,我在公司里搞高并发模块时常用,适合场景简单且对顺序要求极高的线程交替。
🚨但是!我见有些面试者一上来就用 synchronized + wait/notify,想法是好的,但容易写崩:
忘了用 while判断条件或者 notify 写错对象 更坑的是两个线程都 wait 等对方唤醒,然后——挂了,Deadlock!
比如这个例子就可能写挂:
classFooBar{
privateint n;
privateboolean fooTurn = true;
publicFooBar(int n){
this.n = n;
}
publicsynchronizedvoidfoo(Runnable printFoo)throws InterruptedException {
for (int i = 0; i < n; i++) {
while (!fooTurn) {
wait();
}
printFoo.run();
fooTurn = false;
notifyAll();
}
}
publicsynchronizedvoidbar(Runnable printBar)throws InterruptedException {
for (int i = 0; i < n; i++) {
while (fooTurn) {
wait();
}
printBar.run();
fooTurn = true;
notifyAll();
}
}
}
虽然逻辑没毛病,但多个线程调度起来很玄学,线上不推荐这种写法,容易出事。☠️
我实际项目中会更喜欢 Lock + Condition 写法,显式控制更靠谱:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
classFooBar{
privateint n;
private ReentrantLock lock = new ReentrantLock();
private Condition fooCond = lock.newCondition();
private Condition barCond = lock.newCondition();
privateboolean fooTurn = true;
publicFooBar(int n){
this.n = n;
}
publicvoidfoo(Runnable printFoo)throws InterruptedException {
for (int i = 0; i < n; i++) {
lock.lock();
try {
while (!fooTurn) {
fooCond.await();
}
printFoo.run();
fooTurn = false;
barCond.signal();
} finally {
lock.unlock();
}
}
}
publicvoidbar(Runnable printBar)throws InterruptedException {
for (int i = 0; i < n; i++) {
lock.lock();
try {
while (fooTurn) {
barCond.await();
}
printBar.run();
fooTurn = true;
fooCond.signal();
} finally {
lock.unlock();
}
}
}
}
上面这个版本在复杂业务中更稳,特别是当你不止两个线程交替,而是多个任务依赖时,Condition 简直是线程调度中的“神器”。
我曾经在线上处理一个商品价格同步模块,要根据外部接口返回的两个异步数据“合并打印”,这类逻辑就非常依赖这类多线程精细控制。搞不好,就数据错乱,客户直接投诉😵
最后讲个段子缓和一下:有个哥们在面试这题的时候,信誓旦旦地说“我用 Thread.sleep() 控制顺序!”我当场裂开了😂。兄弟啊,你把并发当时间片轮转模拟器了?
总结下这题:
用 Semaphore简洁高效,首推!synchronized + wait/notify不推荐,除非你能把控细节ReentrantLock + Condition在实际项目中更实用,尤其多线程协作逻辑复杂时多线程题,思路最重要,调试手段同样关键!可以手动加日志打印线程名和顺序🪵
💡写题也得像写生产代码一样严谨!这题看似简单,实际可以暴露候选人多线程的理解深度。