Achieving Thread‑Safe Java Performance Tests Without Locks

This article explains why traditional locking harms Java performance‑testing throughput, introduces lock‑free alternatives such as CAS and ThreadLocal, and provides a complete Java demo that shows how each thread can hold its own object to eliminate contention and boost speed.

FunTester
FunTester
FunTester
Achieving Thread‑Safe Java Performance Tests Without Locks

Thread safety and lock‑free alternatives

In Java performance testing, using synchronized blocks or explicit locks can add considerable overhead. Two lock‑free approaches are common: Compare‑And‑Swap (CAS) and ThreadLocal. This article focuses on ThreadLocal.

How ThreadLocal works

ThreadLocal maintains a separate value for each thread. When a thread first calls get(), the initialValue() method is invoked to create the value, which is then stored in a map keyed by the thread. Subsequent calls return the same instance for that thread, eliminating shared mutable state and therefore removing the need for synchronization.

Typical use cases include objects that are expensive to create or that occupy large amounts of memory, and where each thread can safely work with its own copy.

Demo program

The following self‑contained example shows a ThreadLocal that holds a String created from a timestamp (via getNanoMark()) and an empty constant. The hash code of each created string is printed so that the distinct instances can be observed.

package com.fun;

import com.fun.frame.SourceCode;

public class AR extends SourceCode {
    // ThreadLocal that creates a new String for each thread
    static ThreadLocal<String> local = new ThreadLocal<String>() {
        @Override
        public String initialValue() {
            String s = new String(getNanoMark() + EMPTY);
            output(s.hashCode());   // print hash to identify the instance
            return s;
        }
    };

    public static void main(String[] args) throws InterruptedException {
        AR ar = new AR();
        // Calls in the main thread
        ar.ss();
        ar.ss();

        // Two additional threads that also invoke ss()
        Thread t1 = new Thread(() -> ar.ss());
        Thread t2 = new Thread(() -> ar.ss());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }

    // Access the ThreadLocal value
    public void ss() {
        local.get();
    }
}

Expected output

Each thread prints a different hash code, confirming that separate objects were created:

INFO-> 851397249
INFO-> 1851572413
INFO-> 1875674350
Process finished with exit code 0

Practical considerations

ThreadLocal eliminates contention but does not free the stored objects until the thread terminates. In thread‑pool environments you should call local.remove() when the value is no longer needed to avoid memory leaks.

The initial value is created lazily; if the creation is expensive, the cost is incurred only once per thread.

ThreadLocal is appropriate when the object does not need to be shared across threads. If cross‑thread communication is required, other concurrency primitives must be used.

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.

JavaconcurrencyPerformance Testingthread safetyThreadLocallock‑free
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

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.