q篇blogq到了很久,本来是想写另一个跟|络相关bug的查找过E,偷偷懒,写下最q印象比较深ȝbug。这个bug是我的同事水寒最l定位到的?br /> 前几个月同事报告U有一个线上MQ集群会同一旉抛出ArrayIndexOutOfBoundsExceptionq个异常Q也是数组界。查看源码,除去一些无关紧要的l节大概是这样子Q?br />public class ConnectionSelector{
private AtomicInteger sets=new AtomicInteger(0);
public void selectConnection(List<Connection> connList){
if(connList==null)?br /> return null;
?br /> final int size = connList.size();
if (size == 0) {
return null;
}
return connList.get(sets.incrementAndGet() % size);
}
}
很显Ӟq里的本意是实现一个轮询的q接选择器,q回一个选中的连接。用AtomicInteger递增q对链表大小取模Q返回结果烦引位|的q接。异常抛出的位置是我代码中标红的位|?br />
昄Q这里有两种可能Q一U情况下是说在执行那一行代码的时候,connList的大羃了Q也是说连接可能被其他U程UdQ,那么D取模的结果越界。另一U可能是取模的结果本w确实超q了列表范围?br />
W一U情冉|完全可能的,因ؓ服务器的q接可能随时断开或者重q,但是q种情况相对非常见Q因此我们这里ƈ没有对这个选择q程做同步,主要是从性能的角度出发,偶尔的失败可以接受。很遗憾的是Q我被我的思维惯性误gQ从来没有怀疑过W二U情况,L认ؓ是不是真的连接恰巧断开Dq个异常Q但是却无法解释q个异常发生后就一直错误下去,无法自行恢复?br /> Z么说思维惯性误导呢Q这里的问题其实是负数取模的问题Q对一个负数进行取模,l果会是正数q是负数Q答案是l果因语a而异?br /> 我很早以前在使用Ruby的时候做q测试,负数取模l果为正敎ͼ例如在irb里尝试下Q?br />>> -1000%3
=> 2
>> -2001%4
=> 3
q个印象持箋至今Q在clojure里结果也是这样子Q?br />Clojure 1.2.1
user=> (mod -1000 3)
2
user=> (mod -2001 4)
3
可以再试试python:
Python 2.7.1 (r271:86832, Jun 16 2011, 16:59:05)
[GCC 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2335.15.00)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> -10000%3
2
>>> -2001%4
3
q三U语a的结果完全一_l果都ؓ正数。这个惯性思维延箋到java却不成立了,可惜我根本没做测试,让我们试下:
public static void main(final String[] args) {
System.out.println(-1000 % 3);
System.out.println(-2001 % 4);
}
打印l果为:
-1
-1
果然Q在java里负数取模的l果敎ͼ而不是我习惯性地认ؓ是正数。因此最l的定位到的原因是setsq个变量递增过Integer.MAX_VALUE后越界变成负CQ取模的l果敎ͼD抛出数组界的异常,q也解释了ؓ什么同一个集都在同一旉出问题,因ؓq个集群内的机器启动旉盔Rq且调用q个Ҏơ数相对q_?strike>修正问题很简单,加个Math.abs好?/strike>
Update:加个abs是不够的Q因为Math.abs的javadoc提醒了:
Note that if the argument is equal to the value of Integer.MIN_VALUE, the most negative representable int value, the result is that same value, which is negative.
也就是说对Integer.MIN_VALUE做absl果仍然是负数。尽在q个场景中失败一ơ可以接受,但是最好的办法q是回复中steven提到的抵消符号位的做法:
(sets.incrementAndGet() & 0x7FFFFFFF) % size
q个问题更详l的讨论后来我找?a >q篇博客Q作者讨论几U语a和计器的这个问题的l果Q给Z一些结论。不q我觉的q个l论可能也不是那么可靠,特别是对c/c++来说Q很大程度上应该q是依赖于实玎ͼ最可靠的办法还是强制结果ؓ正?br />
q个bug的几个教训:
1、首先是W一ơ出现的时候没有引赯够重视,重启解决问题后没有深I。有句玩W话Q?9Q的E序问题都可以通过重启解决。但是事实上问题仍然存在Q该发生的终I还会发生。不你信不信,它就是发生了Q这是一个奇qV?br />2、注意大脑的思维惯性,l验M和教条主义都不可取。最q在M本好书?a >暗时?/a>》,大脑误导我们的手D可是多U多栗?br />3、最后就是这个负数取模的l果因语a而异Q不要依赖于特定实现?br /> 
]]>