System.currentTimeMillis()在Java中是一個native方法,用于獲取當(dāng)前毫秒時間戳,雖然不用擔(dān)心線程安全問題,但是在超高并發(fā)的調(diào)用時會產(chǎn)生性能瓶頸。通常在記錄操作日志為了獲取時間都會去調(diào)用該方法,如果并發(fā)數(shù)量過大,會發(fā)現(xiàn)累積起來的性能損耗十分嚴(yán)重,為了減少調(diào)用該方法的次數(shù),可以使用時間單例解決。
目前網(wǎng)絡(luò)上流行的一種作法如下:
class SystemClock {
private static final String THREAD_NAME = "system.clock";
private static final SystemClock MILLIS_CLOCK = new SystemClock(1);
private final long precision;
private final AtomicLong now;
private SystemClock(long precision) {
this.precision = precision;
now = new AtomicLong(System.currentTimeMillis());
scheduleClockUpdating();
}
public static SystemClock millisClock() {
return MILLIS_CLOCK;
}
private void scheduleClockUpdating() {
ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor(runnable -> {
Thread thread = new Thread(runnable, THREAD_NAME);
thread.setDaemon(true);
thread.setPriority(Thread.MAX_PRIORITY); //設(shè)置線程優(yōu)先級,讓時間線程盡可能運行(依賴于操作系統(tǒng)的調(diào)度實現(xiàn))
return thread;
});
scheduler.scheduleAtFixedRate(() ->
now.set(System.currentTimeMillis()), precision, precision, TimeUnit.MILLISECONDS);
}
public long now() {
return now.get();
}
}
其實完全不需要依賴定時調(diào)度器ScheduledExecutorService 和線程安全的AtomicLong類,一個簡化的版本如下:
class SystemClock2 {
private static final String THREAD_NAME = "system.clock2";
private static final long TIME_TICK = 1;
private static final SystemClock2 instance = new SystemClock2();
private volatile long now = System.currentTimeMillis();
public SystemClock2() {
Thread timerThread = new Thread(() -> {
while (true) {
try {
TimeUnit.MILLISECONDS.sleep(TIME_TICK);
} catch (InterruptedException e) {
e.printStackTrace();
}
now = System.currentTimeMillis();
}
}, THREAD_NAME);
timerThread.setDaemon(true);
timerThread.setPriority(Thread.MAX_PRIORITY); //設(shè)置線程優(yōu)先級,讓時間線程盡可能運行(依賴于操作系統(tǒng)的調(diào)度實現(xiàn))
timerThread.start();
}
public static SystemClock2 getInstance() {
return instance;
}
public long now() {
return now;
}
}
注:
- 上述兩種實現(xiàn)方式都是按照1毫秒更新一次時間,也可自行按需設(shè)定。
- 上述兩種單例模式都是“餓漢式”而不是“懶漢式”,因為該類被調(diào)用只可能用于獲取時間,因此第一次類初始化后就可以直接啟動時間更新線程了,同時可以減少“懶漢式”單例雙檢鎖(double-check lock)的判空步驟,性能更高。
性能測試程序如下:
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
public class TimeSingleton {
public static void main(String[] args) {
int times = Integer.MAX_VALUE;
new Thread(() -> {
long start = System.currentTimeMillis();
for (long j = 0; j < times; j++) {
SystemClock.millisClock().now();
}
long end = System.currentTimeMillis();
System.out.println("SystemClock Time:" + (end - start) + "毫秒");
}).start();
new Thread(() -> {
long start = System.currentTimeMillis();
for (long j = 0; j < times; j++) {
SystemClock2.getInstance().now();
}
long end = System.currentTimeMillis();
System.out.println("SystemClock2 Time:" + (end - start) + "毫秒");
}).start();
new Thread(() -> {
long start = System.currentTimeMillis();
for (long j = 0; j < times; j++) {
System.currentTimeMillis();
}
long end = System.currentTimeMillis();
System.out.println("SystemCurrentTimeMillis Time:" + (end - start) + "毫秒");
}).start();
}
}
在AMD 1700X,windows10 2004系統(tǒng)下測試結(jié)果:

對比實驗.png