JAVA優化篇1-優化高併發下System.currentTimeMillis()獲取當前時間 1. 目前獲取時間的方式 2. 獲取時間存在的性能隱患 3. 優化 推薦閱讀

1. 目前獲取時間的方式

一般我們獲取當前時間有兩種方式:

new Date().getTime();

或者

System.currentTimeMillis();

但實際上new Date()的源碼中也將調用System.currentTimeMillis();,即:

public Date() {
    this(System.currentTimeMillis());
}

2. 獲取時間存在的性能隱患

System.currentTimeMillis()是native方法,即獲取時間需要和操作系統進行交互(涉及到用戶態與內核態的切換)。而獲取時間完全依賴操作系統,可能獲取到的時間存在精確誤差

單線程下產生延遲說明在系統底層上該線程和其他進程或線程產生了競爭,探究下hotspot中的實現:

jlong os::javaTimeMillis() {
  timeval time;
  int status = gettimeofday(&time, NULL);
  assert(status != -1, "linux error");
  return jlong(time.tv_sec) * 1000  +  jlong(time.tv_usec / 1000);
}

以下是查詢得知,涉及到彙編層面了。

  • 調用gettimeofday()需要從用戶態切換到內核態;
  • gettimeofday()的表現受系統的計時器(時鐘源)影響,在HPET計時器下性能尤其差;
  • 系統只有一個全局時鐘源,高併發或頻繁訪問會造成嚴重的爭用。

3. 優化

高併發性能差的原因一是頻繁的用戶態與內核態的切換,二是爭搶全局時鐘源。那麼我們開啓一個定時輪詢的守護線程的獲取System.currentTimeMillis()時間,其餘線程獲取內存中保存的系統時間。

import java.sql.Timestamp;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 系統時鐘<br>
 * 高併發場景下System.currentTimeMillis()的性能問題的優化
 * System.currentTimeMillis()的調用比new一個普通對象要耗時的多(具體耗時高出多少我還沒測試過,有人說是100倍左右)
 * System.currentTimeMillis()之所以慢是因爲去跟系統打了一次交道
 * 後臺定時更新時鐘,JVM退出時,線程自動回收
 * 
 * see: http://git.oschina.net/yu120/sequence
 * @author lry,looly
 */
public class SystemClock {
    
    /** 時鐘更新間隔,單位毫秒 */
    private final long period;
    /** 現在時刻的毫秒數 */
    private volatile long now;

    /**
     * 構造
     * @param period 時鐘更新間隔,單位毫秒
     */
    public SystemClock(long period) {
        this.period = period;
        this.now = System.currentTimeMillis();
        scheduleClockUpdating();
    }

    /**
     * 開啓計時器線程
     */
    private void scheduleClockUpdating() {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> {
            Thread thread = new Thread(runnable, "System Clock");
            thread.setDaemon(true);
            return thread;
        });
        scheduler.scheduleAtFixedRate(() -> now = System.currentTimeMillis(), period, period, TimeUnit.MILLISECONDS);
    }

    /**
     * @return 當前時間毫秒數
     */
    private long currentTimeMillis() {
        return now;
    }
    
    //------------------------------------------------------------------------ static
    /**
     * 單例
     * @author Looly
     *
     */
    private static class InstanceHolder {
        public static final SystemClock INSTANCE = new SystemClock(1);
    }

    /**
     * 單例實例
     * @return 單例實例
     */
    private static SystemClock instance() {
        return InstanceHolder.INSTANCE;
    }

    /**
     * @return 當前時間
     */
    public static long now() {
        return instance().currentTimeMillis();
    }

    /**
     * @return 當前時間字符串表現形式
     */
    public static String nowDate() {
        return new Timestamp(instance().currentTimeMillis()).toString();
    }
}

推薦閱讀

雪花算法github源碼

驚呆了,高併發下System.currentTimeMillis()竟然有這麼大的問題?!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章