直接干货吧。
什么是 “ 无限死锁”?
这个词是我的叫法,具体应该怎么称呼,知道的童鞋望告知我。我只来说它的现象:进入递归调用后,因为某种超出预期的原因,而使得程序段无法退出,在递归中无限循环执行的这样一种状态,我称之为“无限死锁”。
明明我写的代码没问题啊,为什么会有这种情况存在呢?
所有代码,从出发点上来说,均是基于某种假设,基于某种具体业务逻辑的实现。那么,如果代码本身所基于的环境(数据环境)超出了预期,那么就有可能出现这种情况。
来点代码干货说明下:
protected void siphonAllChildSort(List<Map> sourceList, List<Map> destList, String rootId) {
siphonAllChild(sourceList, destList, rootId);
Collections.sort(destList, new Comparator<Map>() {
@Override
public int compare(Map o1, Map o2) {
return (Integer) o1.get("GROUPLEVEL") - (Integer) o2.get("GROUPLEVEL");
}
});
}
private void siphonAllChild(List<Map> sourceList, List<Map> destList, String rootId) {
if (sourceList == null || sourceList.size() == 0) {
return;
}
for (Map<String, Object> src : sourceList) {
if (rootId.equals(ObjectUtils.toString(src.get("PID")))) {
destList.add(src);
siphonAllChild(sourceList, destList, src.get("ID").toString());
}
}
}
如上的代码,是我在项目中截取到的真实代码段。它的作用,如下:
1,根据根节点 rootId,去 sourceList 中抽取这个根下所有数据,并将其放入 destList 中,每个对象通过 子.PID=父.ID 关系关联。
2,对取得的数据根据 GROUPLEVEL 排序。
对头,看懂代码了,感觉没毛病。然而呢?
其实是理想状态下没毛病,如果 PID 和 ID 通过一系列相互指向,形成循环了呢?是的“递归调用之无限死锁“!甚至,有些情况下,内存被耗尽,系统全面停摆。
那是数据的问题,我的程序没bug!别狡辩了,无论数据如何,你的代码让整个系统死掉了。。。
这种数据怎么来的?
也许开发前台操作这个数据的界面的童鞋没多想,开发出的功能,操作上允许循环指向。
也许客户觉得前台操作麻烦,直接去库里根据自己的理解改了数据,然而手误,输入错了。
很多也许。。。
怎样避免?看代码:
protected void siphonAllChildSort(List<Map> sourceList, List<Map> destList, String rootId) {
siphonAllChild(500, sourceList, destList, rootId);
Collections.sort(destList, new Comparator<Map>() {
@Override
public int compare(Map o1, Map o2) {
return (Integer) o1.get("GROUPLEVEL") - (Integer) o2.get("GROUPLEVEL");
}
});
}
private void siphonAllChild(int loop, List<Map> sourceList, List<Map> destList, String rootId) {
if (sourceList == null || sourceList.size() == 0 || loop == 0) {
return;
}
for (Map<String, Object> src : sourceList) {
if (rootId.equals(ObjectUtils.toString(src.get("PID")))) {
destList.add(src);
siphonAllChild(loop--, sourceList, destList, src.get("ID").toString());
}
}
}
是的,核心就是,传入 loop 参数,并每次对其减一,当 loop == 0 时直接跳出。这样做,loop 的初始值设置就很有技巧了:你得设置一个你觉得能处理完正常数据的值。
这损失了递归调用的无限性!是的,但实际够用即可。虽然很多事情理论上有无限,但实际中,我们并不会用到无限。使用递归,只是为了不丑陋的把同样的代码copy 3次或者4次,仅此而已。
loop值的设置,又始问题回到了原点。还是我开头说过的话,所有的代码,出发点总是基于某种假设。