“不患寡,而患不均”,如果线程优先级“不均”,在CPU繁忙的情况下,优先级低的线程得到执行的机会很小,就可能发生线程“饥饿”;持有锁的线程,如果执行的时间过长,也可能导致“饥饿”问题。简而言之,所谓的线程饥饿就是:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
下面我们举个简单的例子,来一窥线程饥饿:
public class TestStarvation {
private static final Logger log = LoggerFactory.getLogger(TestStarvation.class);
// return deploy result
static String deploy() {
return "deploying"
}
public static void main(String[] args) {
//fixed thread
ExecutorService waiterPool = Executors.newFixedThreadPool(2);
waiterPool.execute(() -> {
log.debug("get Approval...");
//deploy in another thread
Future<String> f = waiterPool.submit(() -> {
log.debug("deploy");
return deploy();
});
try {
log.debug("deploy result: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
waiterPool.execute(() -> {
log.debug("get Approval..");
Future<String> f = waiterPool.submit(() -> {
log.debug("deploy");
return deploy();
});
try {
log.debug("deploy result: {}", f.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
});
}
}
当你运行这段代码时,会发现thread就会hung住, 原因是线程池就2个资源, 都被getApproval 抢占了,在方法内层的 deploy 由于和getApproval用的是同一个线程池,所以它需要等getApproval 释放资源,但是getApproval 需要等待内层的deploy 做完才能释放资源。造成了内层的线程饥饿。
当然上面是个极端例子,我们真实的case 中的原理也是一样,他们用了同一个线程池,而且他们的调用有嵌套,造成了大量的等待。处理线程嵌套的模式都是使用不同的线程池,来保证内部和外部没有竞争。知道了原因我们再定义一个fixed thread pool executorSerice, 把它作为 manifestListResultFuture 运行的线程池,这个问题就解决了。
CompletableFuture<List<Map<String, Object>>> manifestListResultFuture =
CompletableFuture.supplyAsync(() -> manifestService.getManifests(cluster, ns) , executorSerice)
.thenApply(manifestListResult -> {
//do some logic
});
当然,造成线程饥饿的原因不是单一的,Java 中导致饥饿的原因主要有:
1.高优先级线程吞噬所有的低优先级线程的 CPU 时间。
2.线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。
3.线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法),因为其他线程总是被持续地获得唤醒。
那么如何防止出现线程饥饿的情况呢?我们可以采用时间片轮转的方式。可以设置线程的优先级,会映射到下层的系统上面的优先级上,如非特别需要,尽量不要用,防止线程饥饿。想了解更多的解决方案我们可以在动力节点在线的免费视频课程中寻找答案。
提枪策马乘胜追击04-21 20:01
代码小兵92504-17 16:07
代码小兵98804-25 13:57
杨晶珍05-11 14:54