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
Please enter at least three characters to search
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

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

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

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Creating Your Swiss Army Knife on Java Test Stack
  • I Don’t TDD: Pragmatic Testing With Java
  • Spring 5 Web Reactive: Flux, Mono, and JUnit Testing
  • ConcurrentHashMap: Call Only One Method Per Key

Trending

  • How Kubernetes Cluster Sizing Affects Performance and Cost Efficiency in Cloud Deployments
  • Implementing API Design First in .NET for Efficient Development, Testing, and CI/CD
  • AI Speaks for the World... But Whose Humanity Does It Learn From?
  • Orchestrating Microservices with Dapr: A Unified Approach
  1. DZone
  2. Coding
  3. Java
  4. A New Way to Test Your Multithreaded Code With JUnit

A New Way to Test Your Multithreaded Code With JUnit

Currently, when we test multi-thread Java we call the class under test by as many threads possible. This approach has the disadvantage that most of the time our faulty test succeeds which makes debugging multi-threaded bugs a nightmare. I, therefore, developed an open-source tool, vmlens, to make JUnit test of multi-threaded Java deterministic.

By 
Thomas Krieger user avatar
Thomas Krieger
·
Updated Sep. 09, 20 · Tutorial
Likes (15)
Comment
Save
Tweet
Share
71.2K Views

Join the DZone community and get the full member experience.

Join For Free

Currently, when we test multi-thread Java we call the class under test by as many threads possible. And since the test is not deterministic, we repeat this test as often as possible.

This approach has the disadvantage that most of the time our faulty test succeeds which makes debugging multi-threaded bugs a nightmare. I, therefore, developed an open-source tool, vmlens, to make JUnit test of multi-threaded Java deterministic. And to make debugging easier.

The idea is to execute all possible thread interleavings for a given test. And to report the failed thread interleaving which makes debugging possible.

A Test for a Concurrent Counter

The following example shows how to use vmlens to write a test for a concurrent counter. All tests are in the GitHub project vmlens-examples in the package com.vmlens.examples.tutorial.counter.

Java
 




xxxxxxxxxx
1
27


 
1
import com.vmlens.api.AllInterleavings;
2
public class TestCounterNonVolatile {
3
 int i = 0;
4
 @Test
5
 public void test() throws InterruptedException {
6
   try (AllInterleavings allInterleavings = 
7
     new AllInterleavings
8
        ("tutorial.counter.TestCounterNonVolatile");) {
9
     while (allInterleavings.hasNext()) {
10
      i = 0;
11
      Thread first = new Thread(() -> {
12
        i++;
13
      });
14
      Thread second = new Thread(() -> {
15
        i++;
16
      });
17
      first.start();
18
      second.start();
19
 
20
      first.join();
21
      second.join();
22
                 
23
      assertEquals(2,i);
24
     }
25
   }
26
 }
27
}



We increment the field i from two threads. And after both threads are finished we check that the count is 2. The trick is to surround the complete test by a while loop iterating over all thread interleavings using the class AllInterleavings.

vmlens runs as a Java agent and uses byte code transformation to calculate all thread interleavings. Therefore you need to configure vmlens in the maven pom as described here. After running the test, we can see the result of all test runs in the interleave report in the file target/interleave/elements.html.

Interleave interface

Our test, test number 5 with the name tutorial.counter.TestCounterVolatile, failed with a data race. A data race means that the reads and writes to a shared field are not correctly synchronized. Incorrectly synchronized reads and writes can be reordered by the JIT compiler or the CPU. Here can  is important. Typically incorrectly synchronized reads and writes return the correct result. Only under very specific circumstances, often a combination of a specific CPU architecture, a specific JVM, and a specific thread interleaving, lead to incorrect values.

vmlens checks for every field access if it is correctly synchronized to detect data races.

A Test for a Concurrent Volatile Counter

To fix the data race we declare the field as volatile:

Java
 




xxxxxxxxxx
1
26


 
1
public class TestCounterVolatile {
2
 volatile int i = 0;
3
 @Test
4
 public void test() throws InterruptedException {
5
   try (AllInterleavings allInterleavings = 
6
      new AllInterleavings
7
        ("tutorial.counter.TestCounterVolatile");) {
8
      while (allInterleavings.hasNext()) {
9
       i = 0;
10
       Thread first = new Thread(() -> {
11
         i++;
12
       });
13
       Thread second = new Thread(() -> {
14
         i++;
15
       });
16
       first.start();
17
       second.start();
18
 
19
       first.join();
20
       second.join();
21
                 
22
       assertEquals(2,i);
23
      }
24
   }
25
 }
26
}



This fixes the data race but now the assertion fails:

Java
 




xxxxxxxxxx
1


 
1
TestCounterVolatile.test:22 expected:<2> but was:<1>



To see what went wrong we click on the test tutorial.counter.TestCounterVolatile in the interleave report. This shows us the interleaving which went wrong:

TestCounterVolatile report

The bug is that both threads first read the variable i and after that, both update the variable. So the second thread overrides the value of the first one.

A Test With an Atomic Counter

To write a correct concurrent counter we use the class AtomicInteger:

Java
 




x
25


 
1
public class TestCounterAtomic {
2
 AtomicInteger i = new AtomicInteger();
3
 @Test
4
 public void test() throws InterruptedException {
5
   try (AllInterleavings allInterleavings = 
6
      new AllInterleavings
7
         ("tutorial.counter.TestCounterAtomic");) {
8
      while (allInterleavings.hasNext()) {
9
       i.set(0);
10
       Thread first = new Thread(() -> {
11
        i.incrementAndGet();
12
       });
13
       Thread second = new Thread(() -> {
14
        i.incrementAndGet();
15
       });
16
       first.start();
17
       second.start();
18
 
19
       first.join();
20
       second.join();
21
       assertEquals(2,i.get());
22
      }
23
   }
24
  }
25
}



Now the increment of our counter is atomic and our test finally succeeds.

Conclusion

As we have seen executing all thread interleavings for multi-threaded tests make multi-threaded tests deterministic. And it makes debugging of failed tests possible. To test all thread interleavings we have surrounded our test by a while loop iterating over all thread interleavings using the class AllInterleavings. vmlens uses byte code transformation to calculate all thread interleavings. Therefore you also need to configure vmlens in the maven pom as described here. And if a test fails you can look at the failing thread interleaving to debug our test.

Testing Interleaving (disk storage) JUnit Java (programming language)

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

Opinions expressed by DZone contributors are their own.

Related

  • Creating Your Swiss Army Knife on Java Test Stack
  • I Don’t TDD: Pragmatic Testing With Java
  • Spring 5 Web Reactive: Flux, Mono, and JUnit Testing
  • ConcurrentHashMap: Call Only One Method Per Key

Partner Resources

×

Comments
Oops! Something Went Wrong

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
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!