Why System.currentTimeMillis() Can Be Slow Under High Concurrency and How to Optimize It
The article explains that System.currentTimeMillis()—a common Java API for timestamps—can become dramatically slower when called concurrently or very frequently, analyzes the native implementation causing the slowdown, and presents a cached‑clock solution to mitigate the performance issue.
System.currentTimeMillis() is a widely used Java API for timestamps, but under heavy concurrent or frequent calls its performance degrades dramatically, as demonstrated by a benchmark showing parallel calls taking hundreds of times longer than serial calls.
The article provides a Java demo that measures the latency of 100 serial and 100 parallel calls, and shows the source of the native implementation in HotSpot (os_linux.cpp) which uses gettimeofday(), causing a user‑to‑kernel switch and being affected by the Linux clock source (HPET vs TSC).
It explains why the HPET timer performs poorly and how the single global clock source becomes a contention point under high load.
To mitigate the issue, a custom clock that updates the current time in a single scheduled thread is presented. The CurrentTimeMillisClock class caches the timestamp and provides a fast now() method, while a ScheduledThreadPoolExecutor updates the cache every millisecond.
public class CurrentTimeMillisPerfDemo {
private static final int COUNT = 100;
public static void main(String[] args) throws Exception {
// serial benchmark
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");
// parallel benchmark
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch endLatch = new CountDownLatch(COUNT);
for (int i = 0; i < COUNT; i++) {
new Thread(() -> {
try { startLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { endLatch.countDown(); }
System.currentTimeMillis();
}).start();
}
beginTime = System.nanoTime();
startLatch.countDown();
endLatch.await();
elapsedTime = System.nanoTime() - beginTime;
System.out.println("100 System.currentTimeMillis() parallel calls: " + elapsedTime + " ns");
}
} public class CurrentTimeMillisClock {
private volatile long now;
private CurrentTimeMillisClock() { this.now = System.currentTimeMillis(); }
private void scheduleTick() {
new ScheduledThreadPoolExecutor(1).scheduleAtFixedRate(() -> {
now = System.currentTimeMillis();
}, 1, 1, TimeUnit.MILLISECONDS);
}
public long now() { return now; }
public static CurrentTimeMillisClock getInstance() { return SingletonHolder.INSTANCE; }
private static class SingletonHolder { private static final CurrentTimeMillisClock INSTANCE = new CurrentTimeMillisClock(); }
}Usage is as simple as CurrentTimeMillisClock.getInstance().now(). The article notes that such optimization is only necessary in extreme cases where System.currentTimeMillis() becomes a bottleneck.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
