System.currenttimemillis () is an extremely common base Java API, widely used to get timestamps or measure code execution times and so on, which should strike us as lightning fast. But in practice, when it is called concurrently or very frequently (such as a busy interface, or a high-throughput streaming program that requires a timestamp), its performance can be surprising.

Look directly at the code

public class CurrentTimeMillisPerfDemo {
 private static final int COUNT = 100;
 public static void main(String[] args) throws Exception {
 long beginTime = System.nanoTime();
 for (int i = 0; i < COUNT; i++) {
 System.currentTimeMillis();
 }
 long elapsedTime = System.nanoTime() - beginTime;
 System.out.println("100 System.currentTimeMillis() serial calls: " + elapsedTime + " ns");
 CountDownLatch startLatch = new CountDownLatch(1);
 CountDownLatch endLatch = new CountDownLatch(COUNT);
 for (int i = 0; i < COUNT; i++) {
 new Thread(() -> { 
 try {
 startLatch.await();
 System.currentTimeMillis();
 } catch (InterruptedException e) {
 e.printStackTrace();
 } finally {
 endLatch.countDown();
 }
 }).start();
 }
 beginTime = System.nanoTime();
 startLatch.countDown();
 endLatch.await();
 elapsedTime =System.nanoTime() - beginTime;
 System.out.println("100 System.currentTimeMillis() parallel calls: " +elapsedTime + " ns"); }}Copy the code

The following figure shows the result.

As you can see, a concurrent call to System.CurrentTimemillis () one hundred times takes 250 times longer than a single thread call of one hundred times. A similar pattern is observed if the frequency of calls to a single thread increases (to the point of, say, several times per millisecond). In fact, in extreme cases, System.CurrentTimemillis () can take more time than creating a simple object instance, so you can try to replace the thread statement with something like New HashMap<>.

Why is that?

Came to the HotSpot source HotSpot/SRC/Linux OS / / vm/os_linux CPP file, there is a javaTimeMillis () method, which is System. CurrentTimeMillis () native to implement.

Dig source code so far, because there have been foreign big guy in-depth to The assembly level to explore, details can see “The Slow currentTimeMillis()” this article. To put it simply:

  • Calling getTimeofday () requires switching from user mode to kernel mode;
  • Gettimeofday () is affected by Linux timers (clock sources), especially under HPET timers;
  • The system has only one global clock source. High concurrency or frequent access may cause serious contention.

The HPET timer performs poorly because all requests for timestamps are executed serially. TSC timers perform better because they have special registers to hold timestamps. The downside is that it can be unstable, since it is a purely hardware timer with a variable frequency (related to the processor’s CLK signal). Details about the HPET and TSC can see https://en.wikipedia.org/wiki/HighPrecisionEventTimer and https://en.wikipedia.org/wiki/TimeStamp_Counter.

In addition, you can use the following command to view and modify the clock source.

~ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
tsc hpet acpi_pm
~ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc
~ echo 'hpet' > /sys/devices/system/clocksource/clocksource0/current_clocksource
Copy the code

How to solve this problem?

The most common approach is to update the timestamp by millisecond with a single scheduling thread, which is equivalent to maintaining a global cache. Other threads take timestamps from within, eliminating contention for clock resources at the cost of some precision. The specific code is as follows.

public class CurrentTimeMillisClock {
 private volatile long now;
 private CurrentTimeMillisClock() {
 this.now = System.currentTimeMillis();
 scheduleTick();
 }
 private void scheduleTick() {
 new ScheduledThreadPoolExecutor(1, runnable -> {
 Thread thread = new Thread(runnable, "current-time-millis");
 thread.setDaemon(true);
 return thread;
 }).scheduleAtFixedRate(() -> {
 now = System.currentTimeMillis();
 }, 1, 1, TimeUnit.MILLISECONDS);
 }
 public long now() {
 return now;
 }
 public static CurrentTimeMillisClock getInstance() {
 returnSingletonHolder.INSTANCE; } private static class SingletonHolder { private static final CurrentTimeMillisClock INSTANCE = new CurrentTimeMillisClock(); }}Copy the code

When using direct CurrentTimeMillisClock. GetInstance (). Now it is ok to ().

However, when the efficiency of System.currentTimemillis () does not affect the overall efficiency of the program, this optimization is not necessary at all; it is only for extreme cases.