为什么80%的码农都做不了架构师?>>>
问题现象描述
代码中获取时区是一个很平常的操作,例如我们要获取东八区,可以TimeZone.getTimeZone("GMT+08")这样写,也可以写成TimeZone.getTimeZone("GMT+0800"),毫无疑问,这两种写法的结果是一样的。那是不是就说明这两种写法是等价的,进而我们在使用它们时可以随便一会儿用写法一,一会儿用写法二,二者之间随意切换呢?请看下面测试代码,只执行100000次的TimeZone.getTimeZone("GMT+0800")情况下,耗时为34ms。
long start &#61; System.currentTimeMillis();for(int i&#61;0;i<100000;i&#43;&#43;){TimeZone.getTimeZone("GMT&#43;0800");}long end &#61; System.currentTimeMillis();System.out.println("耗时&#xff1a;"&#43;(end - start));
可是当我们在循环执行TimeZone.getTimeZone("GMT&#43;0800")之前&#xff0c;执行一次TimeZone.getTimeZone("GMT&#43;08")&#xff0c;耗时立刻变为2107ms&#xff01;
TimeZone.getTimeZone("GMT&#43;08");long start &#61; System.currentTimeMillis();for(int i&#61;0;i<100000;i&#43;&#43;){TimeZone.getTimeZone("GMT&#43;0800");}long end &#61; System.currentTimeMillis();System.out.println("耗时&#xff1a;"&#43;(end - start));
将GMT&#43;08和GMT&#43;0800的位置互换也会看到相同的现象&#xff0c;也就是说TimeZone.getTimeZone("GMT&#43;0800")和TimeZone.getTimeZone("GMT&#43;08")两种写法虽然得到的结果一样&#xff0c;但两者同时使用时&#xff0c;会相互影响&#xff0c;导致方法耗时增加明显。可千万别小看这种差距&#xff0c;反映到真正的业务应用中可能就是几百几千TPS的差距&#xff01;
问题分析
通过结合源码分析&#xff0c;最终将问题简化如下&#xff1a;
public static void main(String[] args){simulateGetTimeZone("GMT&#43;08");long start &#61; System.currentTimeMillis();for(int i&#61;0;i<100000;i&#43;&#43;){simulateGetTimeZone("GMT&#43;0800");}long end &#61; System.currentTimeMillis();System.out.println("耗时&#xff1a;"&#43;(end - start));}public static void simulateGetTimeZone(String id){ZoneInfoFile.getZoneInfo(id);ZoneInfoFile.getCustomTimeZone(id, 28800000);}
调用simulateGetTimeZone方法产生的现象和调用TimeZone.getTimeZone时是类似的&#xff0c;因此我们此时只要分析为什么 ZoneInfoFile.getZoneInfo和ZoneInfoFile.getCustomTimeZone两个方法同时执行时就会产生问题描述中的奇怪现象。
两个方法的源码如下&#xff0c;为了便于后面分析&#xff0c;在部分代码后添加了标号&#xff1a;
public static ZoneInfo getZoneInfo(String id) {ZoneInfo zi &#61; getFromCache(id);//1if (zi &#61;&#61; null) {zi &#61; createZoneInfo(id);//2if (zi &#61;&#61; null) {return null;}zi &#61; addToCache(id, zi);}return (ZoneInfo) zi.clone();}
public static ZoneInfo getCustomTimeZone(String originalId, int gmtOffset) {String id &#61; toCustomID(gmtOffset);//3ZoneInfo zi &#61; getFromCache(id);//4if (zi &#61;&#61; null) {zi &#61; new ZoneInfo(id, gmtOffset);zi &#61; addToCache(id, zi);//5if (!id.equals(originalId)) {zi &#61; addToCache(originalId, zi);//6}}return (ZoneInfo) zi.clone();}
当先执行simulateGetTimeZone("GMT&#43;08");一次时&#xff0c;第三步得到的id为GMT&#43;08&#xff1a;00&#xff0c;然后分别在第五步和第六步添加id为GMT&#43;08&#xff1a;00和GMT&#43;08的ZoneInfo到缓存中&#xff0c;后续如果继续执行simulateGetTimeZone("GMT&#43;08")&#xff0c;那么在第一步就会返回结果&#xff1b;可是如果后续执行的是simulateGetTimeZone("GMT&#43;0800")&#xff0c;由于没有id为GMT&#43;0800的缓存&#xff0c;所以继续执行到第三步&#xff0c;得到id仍为GMT&#43;08&#xff1a;00&#xff0c;这个id是有缓存的&#xff0c;因此在第四步就可以返回&#xff0c;从而不会添加id为GMT&#43;0800的ZoneInfo到缓存中&#xff0c;也就是说以后每次都要执行到第四步才可以返回结果&#xff0c;而不是执行到第一步就可以直接返回结果。先执行一次simulateGetTimeZone("GMT&#43;08")&#xff0c;会让后面simulateGetTimeZone("GMT&#43;0800")的每一次执行都多执行第一步和第四步之间的步骤&#xff0c;从而方法的耗时也会明显增加&#xff01;