Why System.currentTimeMillis() Slows Down Under High Concurrency and How to Fix It

System.currentTimeMillis() appears fast but under heavy concurrent calls it becomes dramatically slower due to kernel‑mode time‑of‑day queries, and this article explains the root cause, shows benchmark results, and offers a cached‑timestamp solution to restore performance.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Why System.currentTimeMillis() Slows Down Under High Concurrency and How to Fix It

System.currentTimeMillis() is a ubiquitous Java API used for timestamps and measuring execution time, yet when called concurrently or extremely frequently (e.g., in a busy interface or high‑throughput stream), its performance can be surprisingly poor.

Benchmark 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");
    }
}

The parallel run takes about 250 times longer than the serial run, and the slowdown grows with higher call frequencies. In extreme cases, a single System.currentTimeMillis() call can be slower than creating a simple object.

Why does this happen?

The native implementation resides in hotspot/src/os/linux/vm/os_linux.cpp as javaTimeMillis(), which simply calls gettimeofday(). This system call requires a transition from user mode to kernel mode and its performance depends on the Linux clock source. On systems using the HPET timer, gettimeofday() is especially slow, while the TSC timer is faster because it uses a dedicated register, though it may be less stable.

Only one global clock source exists, so high concurrency leads to severe contention.

You can inspect and change the clock source with commands such as:

~ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
# e.g. tsc hpet acpi_pm
~ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
# e.g. tsc
~ echo 'hpet' > /sys/devices/system/clocksource/clocksource0/current_clocksource

How to mitigate the issue

The common remedy is to maintain a cached timestamp updated by a single scheduled thread, allowing other threads to read the value from memory without invoking the costly system call.

public class CurrentTimeMillisClock {
    private volatile long now;
    private CurrentTimeMillisClock() {
        this.now = System.currentTimeMillis();
        scheduleTick();
    }
    private void scheduleTick() {
        new ScheduledThreadPoolExecutor(1, r -> {
            Thread t = new Thread(r, "current-time-millis");
            t.setDaemon(true);
            return t;
        }).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:

long timestamp = CurrentTimeMillisClock.getInstance().now();

This optimization is only worthwhile when the cost of System.currentTimeMillis() becomes a bottleneck; otherwise the added complexity is unnecessary.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendjavaperformanceconcurrencySystem.currentTimeMillis
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.