DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

SBOMs are essential to circumventing software supply chain attacks, and they provide visibility into various software components.

Related

  • Automating Cucumber Data Table to Java Object Mapping in Your Cucumber Tests
  • Projections/DTOs in Spring Data R2DBC
  • Testcontainers With Kotlin and Spring Data R2DBC
  • Fluent Assertions Reloaded

Trending

  • Engineering High-Scale Real Estate Listings Systems Using Golang, Part 1
  • Streamline Your ELT Workflow in Snowflake With Dynamic Tables and Medallion Design
  • Fraud Detection in Mobility Services With Apache Kafka and Flink
  • Beyond Java Streams: Exploring Alternative Functional Programming Approaches in Java
  1. DZone
  2. Coding
  3. Java
  4. How to Test Multi-Threaded and Concurrent Java

How to Test Multi-Threaded and Concurrent Java

This tutorial teaches how to test multi-threaded, concurrent Java using VMLens. An open-source tool to test concurrent Java code in a deterministic and reproducible way.

By 
Thomas Krieger user avatar
Thomas Krieger
·
Jun. 24, 25 · Analysis
Likes (6)
Comment
Save
Tweet
Share
1.9K Views

Join the DZone community and get the full member experience.

Join For Free

Testing multi-threaded, concurrent Java code is difficult because each test run only captures one possible thread interleaving, and those interleavings are non-deterministic. 

To address this, I created the open-source tool VMLens. VMLens allows you to test concurrent Java code in a deterministic and reproducible way by executing all possible thread interleavings.

In this guide, I will show you how to use VMLens with a simple example. We will build a concurrent Address class that holds a street and a city. The class should support parallel reading and writing from multiple threads.

You can download all examples from this Git repository.

The First Problem: A Data Race

Here is our first implementation of the Address:

Java
 
public class Address {

    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }
    public void update(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public String getStreetAndCity() {
        return  street + ", " + city;
    }
}


And the test: 

Java
 
@Test
    public void readWrite() throws InterruptedException {
        try(AllInterleavings allInterleavings = new AllInterleavings("howto.address.regularFieldReadWrite")) {
            while (allInterleavings.hasNext()) {
                // Given
                Address address = new Address("First Street", "First City");

                // When
                Thread first = new Thread() {
                    @Override
                    public void run() {
                        address.update("Second Street","Second City");
                    }
                };
                first.start();
                String streetAndCity = address.getStreetAndCity();;
                first.join();

                // Then
                assertThat(streetAndCity,anyOf(is("First Street, First City"),
                        is("Second Street, Second City")));
            }
        }
    }


The test updates the Address in a newly started thread, while simultaneously reading the Address inside the original thread. We expect to either read the original address if the read happens before the update. Or the updated address if the read happens after the update. We test this using the assertion:

Java
 
assertThat(streetAndCity,anyOf(is("First Street, First City"),
    is("Second Street, Second City")));`


To cover all possible thread interleavings, we wrap the test in a while loop that runs through every possible execution order: 

Java
 
try(AllInterleavings allInterleavings = new AllInterleavings("howto.address.regularFieldReadWrite")) {
   while (allInterleavings.hasNext()) {


Running the test with VMLens leads to the following error: a data race.

Data race

A data race occurs when two threads access the same field at the same time without proper synchronization. Synchronization actions — such as reading or writing a volatile field or using a lock — ensure visibility and ordering between threads. Without synchronization, there’s no guarantee that a thread will see the most recently written value. This is because the compiler may reorder instructions, and CPU cores can cache field values independently. Both synchronization actions and data races are formally defined in the Java Memory Model.

As the trace shows, there is no synchronization action between the read and write to our street and city variables. So VMLens reports a data race. To fix the error, we add a volatile modifier to both fields.

The Second Problem: A Read-Modify-Write Race Condition

Here is the new Address class with the two volatile fields:

Java
 
public class Address {

    private volatile String street;
    private volatile String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }
    public void update(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public String getStreetAndCity() {
        return  street + ", " + city;
    }
}


When we run our test now, the assertion fails:

Plain Text
 
Expected: (is "First Street, First City" or is "Second Street, Second City")     but: was "First Street, Second City"


We read a partially updated Address. The VMLens report reveals the specific thread interleaving that caused this error:

Read Write Race

The main thread first reads the street variable before it has been updated. Meanwhile, another thread updates both the street and city variables. When the main thread later reads the city variable, it ends up seeing a partially updated Address. To solve this, we use a ReenatrantLock.

The Solution: A Lock

For our new test, we add a ReenatrantLock to the update and getStreetAndCity methods:

Java
 
public class Address {

    private final Lock lock = new ReentrantLock();
    private String street;
    private String city;

    public Address(String street, String city) {
        this.street = street;
        this.city = city;
    }

    public void update(String street, String city) {
        lock.lock();
        try{
            this.street = street;
            this.city = city;
        } finally {
            lock.unlock();
        }
    }

    public synchronized String getStreetAndCity() {
        lock.lock();
        try{
            return  street + ", " + city;
        } finally {
            lock.unlock();
        }
    }

}


Now our test succeeds.

What to Test?

When we write a concurrent class, we want the methods of the class to be atomic. This means that we either see the state before or after the method call. We already tested this for the parallel execution of updating and reading our Address class. What is still missing is a test for the parallel update of our class from two threads. This second test is shown below:

Java
 
@Test
    public void writeWrite() throws InterruptedException {
        try(AllInterleavings allInterleavings = new AllInterleavings("howto.address.lockWriteWrite")) {
            while (allInterleavings.hasNext()) {
                // Given
                Address address = new Address("First Street", "First City");

                // When
                Thread first = new Thread() {
                    @Override
                    public void run() {
                        address.update("Second Street","Second City");
                    }
                };
                first.start();
                address.update("Third Street","Third City");
                first.join();

                // Then
                String streetAndCity = address.getStreetAndCity();
                assertThat(streetAndCity,anyOf(is("Second Street, Second City"),
                        is("Third Street, Third City")));
            }
        }
    }


This test also succeeds for the class with the ReenatrantLock.

Tests Are Missing

The number of cores of the CPU is continuously increasing. In 2020, the processor with the highest core count was the AMD EPYC 7H12 with 64 cores and 128 hardware threads. Today, June 2025, the processor with the highest core count has 288 efficiency cores, the Intel Xeon 6 6900E. AMD increased the core count to 128 and 256 hardware threads with the AMD EPYC 9754.

CPU plot

Java with volatile fields, synchronization blocks and the powerful concurrency utilities in java.util.concurrent allows us to use all those cores efficiently. Project Loom with its virtual threads and structured concurrency will further improve this. What is still missing is a way to test that we are using all those techniques correctly.

I hope VMLens can fill this gap. Get started with testing multi-threaded, concurrent Java here.

Java (programming language) Testing Data Types

Published at DZone with permission of Thomas Krieger, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Automating Cucumber Data Table to Java Object Mapping in Your Cucumber Tests
  • Projections/DTOs in Spring Data R2DBC
  • Testcontainers With Kotlin and Spring Data R2DBC
  • Fluent Assertions Reloaded

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: