程序员老鬼

不能加班的人,请离开我的团队。。

刚刚看到一个有意思的吐槽,领导要求不愿意加班的人直接离开,工作时间是从早9点到凌晨2点,尼玛,这是不把人当人呀

Image

作为一名程序员,我真的是有点无语。把这种“加班文化”当成常态来要求,未免太过苛刻了。毕竟,写代码不是拼体力,而是拼脑力。

如果长期超负荷工作,脑力的消耗会大大降低效率,甚至可能导致程序质量下降,出问题反而得不偿失。而且咱就是说,你是真把劳动法当摆设呀。

Image
工作时间的合理安排应该注重效率而非长时间的付出。领导如果真的想带领团队做出好产品,应该注重团队的长期健康,而不是一味要求加班。
程序员又不是机器,劳累过度不仅影响身体健康,也会导致创造力枯竭。【备注:文末可领最新资料】

算法题:交替打印 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 在实际项目中更实用,尤其多线程协作逻辑复杂时
  • 多线程题,思路最重要,调试手段同样关键!可以手动加日志打印线程名和顺序🪵

💡写题也得像写生产代码一样严谨!这题看似简单,实际可以暴露候选人多线程的理解深度。