System.currentTimeMillis() is an extremely common basic Java API. It is widely used to obtain time stamps or measure code execution time. In our impression, it should be as fast as lightning. But in fact, when it is called concurrently or very frequently (such as a busy interface or a streaming program with large throughput that needs to obtain a timestamp), its performance will be amazing.
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"); } }
As you can see, System.currentTimeMillis() is executed in a single thread; Execute System.currentTimeMillis() concurrently than multithreading; Many times faster.
Why is that?
Come to hotspot/src/os of the HotSpot source code/ linux /vm/os_ In the linux.cpp file, there is a javaTimeMillis() method, which is the native implementation of System.currentTimeMillis().
That's all for digging the source code, because some foreign leaders have gone deep into the compilation level to explore. For details, please refer to the article The Slow currentTimeMillis(). In short:
Calling gettimeofday() requires switching from user state to kernel state;
The performance of gettimeofday() is affected by the timer (clock source) of Linux system, especially under HPET timer;
The system has only one global clock source. High concurrency or frequent access will cause serious contention.
The reason for the poor performance of the HPET timer is that all requests for timestamps are executed serially. TSC timer has better performance because there are special registers to save time stamps. The disadvantage is that it may be unstable because it is a pure hardware timer with variable frequency (related to the CLK signal of the processor). For details on HPET and TSC, see https://en.wikipedia.org/wiki/HighPrecisionEventTimer And https://en.wikipedia.org/wiki/TimeStamp_Counter.
How to solve this problem?
The most common approach is to use a single scheduling thread to update the timestamp in milliseconds, which is equivalent to maintaining a global cache. When other threads fetch timestamps, they are equivalent to fetching from memory, which will no longer cause contention for clock resources at the expense of some accuracy. The specific code is as follows.
public class SystemClock { 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, "system.clock"); thread.setDaemon(true); return thread; }); scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), precision, precision, TimeUnit.MILLISECONDS); } public long now() { return now.get(); }
}
You can use SystemClock.millisClock().now() to output the current time when there is a large amount of concurrency. There is a certain accuracy problem, and the result is the efficiency of time acquisition.
Static internal class writing
package cn.ucaner.alpaca.common.util.key; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; /** * Optimization of System.currentTimeMillis() performance in high concurrency scenario * <p><p> * System.currentTimeMillis()The call of new takes much more time than that of an ordinary object (I haven't tested the specific time-consuming, some people say it is about 100 times) < p > * System.currentTimeMillis()The reason why it is slow is that I have to deal with the system once < p > * The background updates the clock regularly. When the JVM exits, the thread automatically recycles < p > * 10 Billion: 43410206210.72815533980582% < p > * 1 Billion: 4699,29162.0344827586207% < p > * 1000 10000: 480,12,40.0% < p > * 100 10000: 50,10,5.0% < p > * @author lry */ public class SystemClock { private final long period; private final AtomicLong now; ExecutorService executor = Executors.newSingleThreadExecutor(); private SystemClock(long period) { this.period = period; this.now = new AtomicLong(System.currentTimeMillis()); scheduleClockUpdating(); } private static class InstanceHolder { public static final SystemClock INSTANCE = new SystemClock(1); } private static SystemClock instance() { return InstanceHolder.INSTANCE; } private void scheduleClockUpdating() { ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(runnable, "System Clock"); thread.setDaemon(true); return thread; } }); scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { now.set(System.currentTimeMillis()); } }, period, period, TimeUnit.MILLISECONDS); } private long currentTimeMillis() { return now.get(); } public static long now() { return instance().currentTimeMillis(); } public static String nowDate() { return new Timestamp(instance().currentTimeMillis()).toString(); } /** * @Description: Just for test * @param args void * @throws InterruptedException * @Autor: Jason - jasonandy@hotmail.com */ public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 100; i++) { System.out.println(nowDate()); Thread.sleep(1000); } } } //Outputs //2018-05-10 15:37:18.774 //2018-05-10 15:37:19.784 //2018-05-10 15:37:20.784 //2018-05-10 15:37:21.785 //2018-05-10 15:37:22.784 //2018-05-10 15:37:23.784 //2018-05-10 15:37:24.785 //2018-05-10 15:37:25.784 //2018-05-10 15:37:26.785 //2018-05-10 15:37:27.786 //2018-05-10 15:37:28.785 //2018-05-10 15:37:29.785 //2018-05-10 15:37:30.785 //2018-05-10 15:37:31.785