程序员老鬼

渐渐能理解为何不愿意雇佣35岁以上程序猿。去年换了份工作,组里4位组员其中3位40+,发现其实最大的问题并不是说精力不济卷不动

刚看到个贴子,楼主说去年跳槽,新组4个人有3个40+,这才慢慢理解为什么很多公司不愿意招35+程序员。

Image

贴子里大概意思是:这些前辈也不至于躺平,就是明显对新东西兴致一般,更在意稳定、下班时间和家庭节奏。网友们的回复我看了看,一边骂老板年龄歧视,一边又吐槽“老油条混日子”,两边都挺极端的。

我觉得这事吧,核心还真不在年龄,而在“心气”和“更新速度”。有的人30出头就开始摆烂,有的人40+还在啃英文文档、折腾新框架。企业算的是性价比:你要价高,就得么能扛事、么能带人、么能搞定复杂业务,不能只守着十年前那点老经验。

面试题:红绿灯路口

昨天晚上下班开车回家,在小区门口那个十字路口,又红灯 90 秒,绿灯 30 秒,我在那儿干瞪眼,就开始瞎想:要是这玩意儿让我用 Java 写个“红绿灯路口算法”,得怎么设计,程序才不撞车、不卡死、还好扩展。

你先想象这么个场景啊: 一个标准十字路口,只有两种车流方向:

  • 南北直行一波
  • 东西直行一波

先别管左转右转和行人,面试一般也不会一上来就要你全搞定。

我们把每辆车当成一个线程,当车到路口的时候,会调一个方法 carArrive,里边要完成两件事:

  1. 看看自己这条路现在是不是绿灯
  2. 是绿灯就过,不是就老老实实在那儿等

但是注意,有两个坑:

  • 不能让南北和东西同时通行,不然就对撞了
  • 不能让某个方向永远等不到绿灯,不然就“饿死”了

所以核心问题,其实就是“怎么用多线程同步原语,安全地切换红绿灯”。

我一般会这么干:把路口抽象成一个 TrafficLight 类,里面只有一把锁 + 当前绿灯方向 + 两个条件队列(南北一队,东西一队)。

先来个小枚举,别整一堆魔法字符串:

enum Direction {
    NORTH_SOUTH,
    EAST_WEST
}

路口的核心类长这样:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

publicclassTrafficLight{

privatefinal ReentrantLock lock = new ReentrantLock(true); // 公平一点
// 当前是哪个方向绿灯,默认先让南北走
private Direction green = Direction.NORTH_SOUTH;

privatefinal Condition nsCond = lock.newCondition();
privatefinal Condition ewCond = lock.newCondition();

// 车来了就调这个
publicvoidcarArrive(Direction dir, Runnable cross)throws InterruptedException {
        lock.lock();
try {
// 不是自己方向的绿灯,就等
while (dir != green) {
if (dir == Direction.NORTH_SOUTH) {
                    nsCond.await();
                } else {
                    ewCond.await();
                }
            }
// 真正过路的动作交给调用方
            cross.run();
        } finally {
            lock.unlock();
        }
    }

// 红绿灯时间到了,定时调这个方法切换方向
publicvoidswitchLight(){
        lock.lock();
try {
            green = (green == Direction.NORTH_SOUTH)
                    ? Direction.EAST_WEST
                    : Direction.NORTH_SOUTH;
// 切给谁,就把谁那一队全唤醒
if (green == Direction.NORTH_SOUTH) {
                nsCond.signalAll();
            } else {
                ewCond.signalAll();
            }
        } finally {
            lock.unlock();
        }
    }
}

上面这个逻辑你脑补一下现场:

  • 每辆车就是一个线程,来了就 carArrive(direction, () -> print("我通过了"))
  • 如果刚好是自己的绿灯,直接 run,咻一下就过去了
  • 如果不是,就在对应的 Condition 上 await() 挂着休眠,不占 CPU
  • 外面再开一个“红绿灯控制器”线程,每隔 30 秒调一次 switchLight(),轮流放行两个方向

控制器大概长这样:

publicclassLightControllerimplementsRunnable{

privatefinal TrafficLight trafficLight;
privatefinallong greenDurationMillis;

publicLightController(TrafficLight trafficLight, long greenDurationMillis){
this.trafficLight = trafficLight;
this.greenDurationMillis = greenDurationMillis;
    }

@Override
publicvoidrun(){
try {
while (true) {
                Thread.sleep(greenDurationMillis);
                trafficLight.switchLight();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

主程序一启动:

publicclassMain{
publicstaticvoidmain(String[] args){
        TrafficLight trafficLight = new TrafficLight();
new Thread(new LightController(trafficLight, 30_000)).start();

// 模拟一堆车乱七八糟地来
for (int i = 0; i < 100; i++) {
            Direction dir = (i % 2 == 0)
                    ? Direction.NORTH_SOUTH
                    : Direction.EAST_WEST;
int carId = i;
new Thread(() -> {
try {
                    trafficLight.carArrive(dir, () ->
                            System.out.println("car " + carId + " " + dir + " pass"));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }).start();
        }
    }
}

这个版本有几个点是面试官爱追问的:

  • 安全性:同一时刻只会有一个方向被放行,因为 green 在锁保护下修改,while (dir != green) 保证了只有绿灯方向能往下执行
  • 性能:没用 synchronized 轮询那种土办法,而是 Condition.await(),线程乖乖睡觉,唤醒才干活
  • 公平性:用了 ReentrantLock(true),虽然不能保证绝对平均,但至少不至于长时间饿死一边

顺便有一次我们线上做数据库压测,看过一份报告里专门对高并发更新做过对比,说 Postgres 在热点行更新场景里能把 MySQL 打出一个量级,这种“热点+排队”的问题本质上跟路口很像,只不过那边排的是行锁,这边排的是车

真要往现实靠,东西多得一塌糊涂:左转右转、掉头、行人、应急车、公交优先、感应式红绿灯……不过面试里你可以顺嘴说说自己的思路,显得脑子还在转:

  • 左转/直行/右转怎么建模: 可以把“一个方向的一种动作”当成一个节点,比如“北向直行”“东向左转”,然后事先画一张“冲突表”:哪些动作可以同时放行,哪些必须互斥。算法层面就是一个冲突矩阵 conflict[i][j]。

  • 应急车优先: 可以给每个方向挂一个优先级队列,队头是救护车、消防车这类高优先级车,绿灯切换时先放这几辆,再放普通车。

  • 智能一点的红灯时间: 现在我们是死板的 30 秒一切,其实完全可以根据等待队列长度动态调整,比如南北方向排了 50 辆车,东西才 3 辆,那下一轮就多给南北一点时间。甚至可以加个上限,保证东西不会一直饿着。

这些东西不一定真要你写代码,但你能把这个路口从一个 if-else 想到“冲突图 + 调度策略”,面试官一般就知道:你不仅会写锁,还能从系统角度去想资源调度。

行了,我这会儿再不睡明天早会就要趴着写代码了,你要真把上面这套敲一遍跑跑,多线程那块心里就稳很多了。

-END-