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处使用,这样说不定哪天就掉到文章一开始描述的那个坑里面了……

转载于:https://my.oschina.net/zjg23/blog/668692

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值