Is System.currentTimeMillis Really a Performance Bottleneck? A Deep Dive
This article investigates whether System.currentTimeMillis suffers from performance problems by examining custom caching implementations, analyzing common misconceptions, presenting benchmark data from both simple loops and JMH tests, and concluding that the native method is sufficiently fast for typical use cases.
Question: Does System.currentTimeMillis Have Performance Issues?
While examining the source code of a middleware, I discovered that it does not call System.currentTimeMillis() directly but uses a custom cached clock class, which made me wonder if the native method is truly that slow.
/**
* Weak‑precision timer that avoids synchronization for performance.
*/
public class TimeUtil {
// Cached current millisecond value
private static volatile long CURRENT_TIME = System.currentTimeMillis();
public static final long currentTimeMillis() { return CURRENT_TIME; }
public static final long currentTimeNanos() { return System.nanoTime(); }
// Update cache
public static final void update() { CURRENT_TIME = System.currentTimeMillis(); }
}
// A scheduled task updates the cache every second
heartbeatScheduler.scheduleAtFixedRate(processorCheck(), 0L, 1000, TimeUnit.MILLISECONDS);Searching for "currentTimeMillis performance" yields many articles claiming that System.currentTimeMillis is 100 times slower than creating a simple object and 250 times slower under concurrency.
Understanding the Alleged Performance Problems
It accesses the system clock, a critical resource that can cause contention in multithreaded scenarios.
It requires a transition to kernel mode, i.e., "talking to the system".
Some benchmark reports claim massive slowdown under concurrency.
These claims contain several flaws:
Reading the wall‑time ( xtime ) on Linux uses a sequence lock, not a mutex, so concurrent reads do not interfere with each other.
Sequence locks solve the ABA problem by assigning a sequence number to each write; reads compare the sequence before and after reading the data. On 64‑bit machines the read is atomic; on 32‑bit machines it may require two reads.
Creating a new object also involves a system call (memory allocation), so the claim that reading the clock is 100× slower than object allocation is unfounded.
The presented benchmark counts the time spent releasing a latch ( CountDownLatch ) which itself uses CAS and can dominate the measured cost, especially under heavy contention. Moreover, comparing total execution time of a multithreaded run with a single‑threaded run is misleading; per‑call latency should be compared instead.
long begin = System.nanoTime();
// single call to System.currentTimeMillis()
long end = System.nanoTime();
sum += end - begin;Recording the total time per call (including System.nanoTime() ) is fair because both the concurrent and single‑threaded tests use the same measurement.
Data Shows System.currentTimeMillis Performance Is Fine
Improved benchmarks (see code at the end) compare the native method with the cached clock under both single‑threaded and 200‑threaded scenarios.
Iterations | Single‑Thread System | Single‑Thread Cache | 200‑Thread System | 200‑Thread Cache
1w | 3.682 ms | 42.844 ms | 0.583 ms | 0.444 ms
10w | 6.780 ms | 35.837 ms | 3.379 ms | 3.066 ms
100w | 30.764 ms | 70.917 ms | 36.416 ms | 27.906 ms
1000w | 263.287 ms | 427.319 ms | 355.452 ms | 261.360 ms"System" denotes System.currentTimeMillis() ; "CacheClock" denotes the static cached implementation; "200‑Thread" uses Tomcat's default thread pool size.
JMH Benchmark Results
Using JMH (Java Microbenchmark Harness) with 8 threads (double the CPU count) yields the following average times:
Test | Avg Time (µs)
System.currentTimeMillis | 0.368 ± 0.667
CacheClock.currentTimeMillis | 0.578 ± 1.039JMH runs with double‑CPU threads, warm‑up iterations, and measures average time per operation.
Test Code
public class CurrentTimeMillisTest {
public static void main(String[] args) {
int num = 10000000;
System.out.print("Single‑thread " + num + " calls System.currentTimeMillis total time: ");
System.out.println(singleThreadTest(() -> { long l = System.currentTimeMillis(); }, num));
System.out.print("Single‑thread " + num + " calls CacheClock.currentTimeMillis total time: ");
System.out.println(singleThreadTest(() -> { long l = CacheClock.currentTimeMillis(); }, num));
System.out.print("Concurrent " + num + " calls System.currentTimeMillis total time: ");
System.out.println(concurrentTest(() -> { long l = System.currentTimeMillis(); }, num));
System.out.print("Concurrent " + num + " calls CacheClock.currentTimeMillis total time: ");
System.out.println(concurrentTest(() -> { long l = CacheClock.currentTimeMillis(); }, num));
}
private static long singleThreadTest(Runnable r, int num) {
long sum = 0;
for (int i = 0; i < num; i++) {
long begin = System.nanoTime();
r.run();
long end = System.nanoTime();
sum += end - begin;
}
return sum;
}
private static long concurrentTest(Runnable r, int num) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(200, 200, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(num));
long[] sum = new long[]{0};
CountDownLatch latch = new CountDownLatch(num);
for (int i = 0; i < num; i++) {
executor.submit(() -> {
long begin = System.nanoTime();
r.run();
long end = System.nanoTime();
synchronized (CurrentTimeMillisTest.class) {
sum[0] += end - begin;
}
latch.countDown();
});
}
try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); }
return sum[0];
}
public static class CacheClock {
private static final ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1);
private static volatile long timeMillis;
static { timer.scheduleAtFixedRate(() -> timeMillis = System.currentTimeMillis(), 0, 1000, TimeUnit.MILLISECONDS); }
public static long currentTimeMillis() { return timeMillis; }
}
}JMH Benchmark Code
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Warmup(iterations = 120, time = 1, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(time = 1, timeUnit = TimeUnit.MICROSECONDS)
@Threads(8)
@Fork(1)
@State(Scope.Benchmark)
public class JMHTest {
@Benchmark
public long testSystem() { return System.currentTimeMillis(); }
@Benchmark
public long testCacheClock() { return JMHTest.CacheClock.currentTimeMillis(); }
public static class CacheClock {
private static final ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1);
private static volatile long timeMillis;
static { timer.scheduleAtFixedRate(() -> timeMillis = System.currentTimeMillis(), 0, 1000, TimeUnit.MILLISECONDS); }
public static long currentTimeMillis() { return timeMillis; }
}
}Conclusion
In realistic workloads, System.currentTimeMillis() does not exhibit the severe performance degradation claimed by many articles; it even outperforms a naïve cached implementation in both single‑threaded and short‑term concurrent scenarios. Therefore, adding a custom cache clock is unnecessary for most applications.
For accurate micro‑benchmarking, use JMH and follow its best practices.
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.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
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.
