神刀安全网

每日一搏 | TimeZone 的一个坑

问题现象描述

代码中获取时区是一个很平常的操作,例如我们要获取东八区,可以TimeZone.getTimeZone("GMT+08")这样写,也可以写成TimeZone.getTimeZone("GMT+0800"),毫无疑问,这两种写法的结果是一样的。那是不是就说明这两种写法是等价的,进而我们在使用它们时可以随便一会儿用写法一,一会儿用写法二,二者之间随意切换呢?请看下面测试代码,只执行100000次的TimeZone.getTimeZone("GMT+0800")情况下,耗时为34ms。

long start = System.currentTimeMillis();     for(int i=0;i<100000;i++)     {         TimeZone.getTimeZone("GMT+0800");     }     long end = System.currentTimeMillis();     System.out.println("耗时:"+(end - start));

可是当我们在循环执行TimeZone.getTimeZone("GMT+0800")之前,执行一次TimeZone.getTimeZone("GMT+08"),耗时立刻变为2107ms!

TimeZone.getTimeZone("GMT+08");           long start = System.currentTimeMillis();     for(int i=0;i<100000;i++)     {         TimeZone.getTimeZone("GMT+0800");     }     long end = System.currentTimeMillis();     System.out.println("耗时:"+(end - start));

将GMT+08和GMT+0800的位置互换也会看到相同的现象,也就是说TimeZone.getTimeZone("GMT+0800")和TimeZone.getTimeZone("GMT+08")两种写法虽然得到的结果一样,但两者同时使用时,会相互影响,导致方法耗时增加明显。可千万别小看这种差距,反映到真正的业务应用中可能就是几百几千TPS的差距!

问题分析

通过结合源码分析,最终将问题简化如下:

public static void main(String[] args)     {     simulateGetTimeZone("GMT+08");           long start = System.currentTimeMillis();     for(int i=0;i<100000;i++)     {         simulateGetTimeZone("GMT+0800");     }     long end = System.currentTimeMillis();     System.out.println("耗时:"+(end - start));       }           public static void simulateGetTimeZone(String id)     {     ZoneInfoFile.getZoneInfo(id);     ZoneInfoFile.getCustomTimeZone(id, 28800000);     }

调用simulateGetTimeZone方法产生的现象和调用TimeZone.getTimeZone时是类似的,因此我们此时只要分析为什么 ZoneInfoFile.getZoneInfo和ZoneInfoFile.getCustomTimeZone两个方法同时执行时就会产生问题描述中的奇怪现象。两个方法的源码如下,为了便于后面分析,在部分代码后添加了标号:

public static ZoneInfo getZoneInfo(String id) {         ZoneInfo zi = getFromCache(id);//1         if (zi == null) {             zi = createZoneInfo(id);//2             if (zi == null) {                 return null;             }             zi = addToCache(id, zi);         }         return (ZoneInfo) zi.clone();     }
public static ZoneInfo getCustomTimeZone(String originalId, int gmtOffset) {       String id = toCustomID(gmtOffset);//3         ZoneInfo zi = getFromCache(id);//4       if (zi == null) {           zi = new ZoneInfo(id, gmtOffset);           zi = addToCache(id, zi);//5           if (!id.equals(originalId)) {               zi = addToCache(originalId, zi);//6           }       }       return (ZoneInfo) zi.clone();   }

当先执行simulateGetTimeZone("GMT+08");一次时,第三步得到的id为GMT+08:00,然后分别在第五步和第六步添加id为GMT+08:00和GMT+08的ZoneInfo到缓存中,后续如果继续执行simulateGetTimeZone("GMT+08"),那么在第一步就会返回结果;可是如果后续执行的是simulateGetTimeZone("GMT+0800"),由于没有id为GMT+0800的缓存,所以继续执行到第三步,得到id仍为GMT+08:00,这个id是有缓存的,因此在第四步就可以返回,从而不会添加id为GMT+0800的ZoneInfo到缓存中,也就是说以后每次都要执行到第四步才可以返回结果,而不是执行到第一步就可以直接返回结果。先执行一次simulateGetTimeZone("GMT+08"),会让后面simulateGetTimeZone("GMT+0800")的每一次执行都多执行第一步和第四步之间的步骤,从而方法的耗时也会明显增加!

问题引申

获取时区虽是一个简单的操作,但如果不注意里面的细节,对应用性能的影响将会是巨大的。对这种常量数据应该定义为静态的,且要一处定义,N处使用;切不可N处定义,N处使用,这样说不定哪天就掉到文章一开始描述的那个坑里面了……

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » 每日一搏 | TimeZone 的一个坑

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址