好多央国企,工资到手才四五千,里面一大堆都是硕士研究生学历,工资明明这么低,为啥央国企还能把这帮高学历的年轻人稳稳留住?
央国企到手四五千,放外面看确实不高,尤其一看名单,硕士一抓一把,985、211也不少。你说他们图啥?真图那几千块工资吗?大概率不是。
很多人图的是稳。不是嘴上说说那种稳,是房贷敢办、家里人放心、对象父母听了不皱眉的稳。外面互联网一年多拿几万,看着香,但今天优化、明天转岗,后天部门没了,心脏不好的真扛不住。
还有些岗位,工资低归低,福利、食堂、补贴、公积金、年假这些加一起,账不能只看到账短信。更别说有些人进去后,压根不是奔着短期发财去的,是想慢慢熬资历、熬编制感、熬个不用天天被KPI追着跑的生活。
所以这事儿离谱吗?有点。但你要说没人懂账,那也不至于。大家都不傻,只是怕的东西不一样。
线程明明睡了 100ms,日志里却只隔了 83ms。
这种题别急着写 Thread.sleep(millis)。面试里问“睡眠函数”,通常不是考你会不会调 API,而是想看你知不知道:睡眠不是精确定时器,线程可能被中断,也可能提前醒,系统调度还会把时间拖长。
题目可以简化成一句话:
给一个毫秒数 millis,让当前线程至少等待这么久,再继续往下执行。
我一般不会直接写成这样:
Thread.sleep(millis);
这行代码当然能跑,但它有两个问题。
第一,中断怎么处理没说清楚。
第二,sleep 被中断后会直接抛异常,实际等待时间可能不够。你要的是“至少睡够”,还是“被打断就结束”,这两个语义差很多。
我更愿意把代码写成这种:
import java.util.concurrent.locks.LockSupport;
publicclassQuietSleeper{
publicstaticbooleansleepAtLeast(long millis){
if (millis <= 0) {
returntrue;
}
long deadline = System.nanoTime() + millis * 1_000_000L;
boolean interrupted = false;
while (true) {
long left = deadline - System.nanoTime();
if (left <= 0) {
break;
}
LockSupport.parkNanos(left);
if (Thread.interrupted()) {
interrupted = true;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
return !interrupted;
}
}
这里有几个地方别写歪。
我没有用 currentTimeMillis(),因为它是墙上时间,可能被系统时间校准影响。比如机器时间被 NTP 往回拨了一下,你这个睡眠逻辑就很难看。nanoTime() 看的是相对时间,更适合做间隔计算。
还有一个点,parkNanos 不是承诺睡满。它可能提前返回。所以外面必须套一层循环,醒了就重新算还剩多久。这个细节不少人会漏。
测试的时候可以这么跑:
publicclassSleepTest{
publicstaticvoidmain(String[] args){
long start = System.nanoTime();
boolean clean = QuietSleeper.sleepAtLeast(300);
long costMs = (System.nanoTime() - start) / 1_000_000;
System.out.println("cost=" + costMs + "ms, clean=" + clean);
}
}
正常输出大概是:
cost=301ms, clean=true
不一定刚好 300ms。这里别较真,Java 线程不是单片机定时器,调度权在操作系统手里。你能保证的是“不少于目标时间”,不能保证“正好这个时间”。
再看中断场景:
Thread t = new Thread(() -> {
boolean ok = QuietSleeper.sleepAtLeast(2000);
System.out.println("sleep result=" + ok);
System.out.println("interrupt flag=" + Thread.currentThread().isInterrupted());
});
t.start();
Thread.sleep(200);
t.interrupt();
这段代码里,我没有在被中断后立刻退出,而是继续睡够 2 秒,然后把中断标记补回去。
为什么这么写?
因为这个函数的名字叫 sleepAtLeast,语义就是至少睡够。中断这件事不能吞,吞了后面业务线程池、任务取消、网关超时都会变得很难查。所以最后要 interrupt() 补回去。
如果题目要求“被中断就停止等待”,那代码反而更短:
publicstaticbooleansleepUntilInterrupted(long millis){
if (millis <= 0) {
returntrue;
}
try {
Thread.sleep(millis);
returntrue;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
returnfalse;
}
}
这两段代码没有谁高级谁低级,关键看题目语义。
手写睡眠函数真正要盯的就三件事:时间源别用错,提前醒要重算,中断别吞掉。
这题看着小,线上很多定时轮询、失败重试、限流退避,最后都栽在这些小地方。