Table of Contents
Overview
- In this tutorial we use ExecutorService, Callable API and JUNIT to test your program for thread safty
- Callable vs Runable
- Runnable interface is older than Callable, there from JDK 1.0, while Callable is added on Java 5.0.
- Runnable interface has run() method to define task while Callable interface uses call() method for task definition.
- run() method does not return any value, it’s return type is void while call method returns value. Callable interface is a generic parameterized interface and Type of value is provided, when instance of Callable implementation is created.
- Another difference on run and call method is that run method can not throw checked exception, while call method can throw checked exception in Java.
Test case details
- Assume on of your developer has created the following ThreadCounter class.
- This class works fine in single threaded Apps but fails if more than 1 Thread is using this Counter
JAVA code : JUnitTest/src/main/java/com/hhu/junittest/ThreadCounter.java
package com.hhu.junittest;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
@ManagedBean
@SessionScoped
public class ThreadCounter
{
private int count = 0;
public final VolatileInt vi = new VolatileInt();
public ThreadCounter()
{
}
public ThreadCounter( int count)
{
this.count = count;
}
public void setCounter( int count)
{
this.count = count;
}
public static void main(String[] args)
{
System.out.println("Hallo from ThreadCounter Class");
// TODO code application logic here
// final VolatileInt vi = new VolatileInt();
ThreadCounter tc = new ThreadCounter();
tc.setCounter(100*1000*1000);
for (int j = 0; j < tc.count; j++)
tc.testIncrement();
tc. counterStatus();
}
public void testIncrement()
{
vi.num++;
}
public int getCounter()
{
return vi.num;
}
static class VolatileInt
{
volatile int num = 0;
}
public void counterStatus()
{
System.out.printf("Total %,d but was %,d", count, getCounter() );
if ( count == vi.num )
System.out.printf(" - Test : OK \n");
else
System.out.printf(" - Test : FAILED \n");
}
}
The single Thread test works fine and returns :
Hallo from ThreadCounter Class
Total 100,000,000 but was 100,000,000 - Test : OK
Test Code for testing thread-safty : JUnitTest/src/test/java/ThreadTest.java
JAVA Code :
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicInteger;
import com.hhu.junittest.ThreadCounter;
import org.junit.Assert;
import org.junit.Test;
public class ThreadTest
{
/*
static class VolatileInt
{
volatile int num = 0;
}
*/
private void test(final int threadCount, boolean atomicTest ) throws InterruptedException, ExecutionException
{
final boolean atomicTestf = atomicTest;
// final VolatileInt vi = new VolatileInt(); // This test fails for threadCount > 1
final AtomicInteger num = new AtomicInteger(); // This test works for any threadCount value
final int count = 100 *1000*1000;
final ThreadCounter tc = new ThreadCounter(100*1000*1000);
// Create an Anonymous Innner Class with a Callable Object
// Use Callable object so we get some results back from our thread test
Callable<String> task = new Callable<String>()
{
@Override
public String call()
{
for (int j = 0; j < count; j += threadCount)
{
if (atomicTestf)
num.incrementAndGet();
else
// vi.num++;
tc.testIncrement();
}
return "atomicTest: " + atomicTestf + " - Leaving Thread: " + Thread.currentThread().getName() +
" - nThreads: " + threadCount;
}
};
// As we want to run the same Callable Objects on all threads use: Collections.nCopies
// Collections.nCopies returns an immutable list consisting of n copies of the specified object.
// Note the newly allocated data object contains only a single reference to the data object.
List<Callable<String>> tasks = Collections.nCopies(threadCount, task);
//Get ExecutorService from Executors utility class, thread pool size is threadCount
ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
// The invokeAll() method invokes all of the Callable objects you pass to it in the collection passed as parameter.
// The invokeAll() returns a list of Future objects via which you can obtain the results of the executions of each Callable.
List<Future<String>> futures = executorService.invokeAll(tasks);
// create new and empty String ArrayList
List<String> resultList = new ArrayList<String>(futures.size());
// Check for exceptions - our Futures are returning String Objects
for (Future<String> future : futures)
{
// Throws an exception if an exception was thrown by the task.
resultList.add(future.get());
}
// Note the future.get() blocks until we get some return value
// At this stage all tasks are finished - either with an Exception or be a regular method return
// Validate the final Thread Counter status
if (atomicTest)
{
System.out.printf("With %,d threads should total %,d but was %,d",
threadCount , count, num.intValue() );
if ( count == num.intValue() )
System.out.printf(" - Test : OK \n");
else
System.out.printf(" - Test : FAILED \n");
Assert.assertEquals(count,num.intValue());
}
else
{
System.out.printf("With %,d threads should total %,d but was %,d",
threadCount , count, tc.getCounter() );
if ( count == tc.getCounter() )
System.out.printf(" - Test : OK \n");
else
System.out.printf(" - Test : FAILED \n");
Assert.assertEquals(count, tc.getCounter());
}
// Display the results
for ( int i = 0; i<resultList.size(); i++)
System.out.println(resultList.get(i) );
}
@Test
public void test01() throws InterruptedException, ExecutionException
{
test(1,false);
}
@Test
public void test02() throws InterruptedException, ExecutionException
{
test(2,false);
}
@Test
public void test04() throws InterruptedException, ExecutionException
{
test(4,false);
}
@Test
public void test08() throws InterruptedException, ExecutionException
{
test(8,false);
}
@Test
public void test16() throws InterruptedException, ExecutionException
{
test(8,true);
}
}
Test results
[oracle@wls1 JUnitTest]$ mvn test ------------------------------------------------------- T E S T S ------------------------------------------------------- Running ThreadTest With 1 threads should total 100,000,000 but was 100,000,000 - Test : OK atomicTest: false - Leaving Thread: pool-1-thread-1 - nThreads: 1 With 2 threads should total 100,000,000 but was 74,553,473 - Test : FAILED With 4 threads should total 100,000,000 but was 40,718,556 - Test : FAILED With 8 threads should total 100,000,000 but was 23,405,246 - Test : FAILED With 8 threads should total 100,000,000 but was 100,000,000 - Test : OK atomicTest: true - Leaving Thread: pool-5-thread-1 - nThreads: 8 atomicTest: true - Leaving Thread: pool-5-thread-2 - nThreads: 8 atomicTest: true - Leaving Thread: pool-5-thread-3 - nThreads: 8 atomicTest: true - Leaving Thread: pool-5-thread-4 - nThreads: 8 atomicTest: true - Leaving Thread: pool-5-thread-5 - nThreads: 8 atomicTest: true - Leaving Thread: pool-5-thread-6 - nThreads: 8 atomicTest: true - Leaving Thread: pool-5-thread-7 - nThreads: 8 atomicTest: true - Leaving Thread: pool-5-thread-8 - nThreads: 8 Tests run: 5, Failures: 3, Errors: 0, Skipped: 0, Time elapsed: 2.253 sec <<< FAILURE! Results : Failed tests: test02(ThreadTest): expected:<100000000> but was:<74553473> test04(ThreadTest): expected:<100000000> but was:<40718556> test08(ThreadTest): expected:<100000000> but was:<23405246> Tests run: 5, Failures: 3, Errors: 0, Skipped: 0
Some comments on above test results
- test01 works as we use only a single Thread
- test02, test04, test08 fails if using 2,4 or 8 Threads and if using volatile int as ThreadCounter
- test08 works even for 8 Threads if using AtomicInteger() instead of volatile int as ThreadCounter
- test08 with AtomicInteger() returns a String object for each Thread by using our Future objects