1. Overview
In this tutorial, we’ll run through different options available for passing parameters to a Java thread.
2. Thread Fundamentals
As a quick reminder, we can create a thread in Java by implementing Runnable or Callable.
To run a thread, we can invoke Thread#start (by passing an instance of Runnable) or use a thread pool by submitting it to an ExecutorService.
Neither of these approaches accepts any extra parameters, though.
Let’s see what can we do to pass parameters to a thread.
3. Sending Parameters in the Constructor
The first way we can send a parameter to a thread is simply providing it to our Runnable or Callable in their constructor.
Let’s create an AverageCalculator that accepts an array of numbers and returns their average:
public class AverageCalculator implements Callable<Double> {
int[] numbers;
public AverageCalculator(int... numbers) {
this.numbers = numbers == null ? new int[0] : numbers;
}
@Override
public Double call() throws Exception {
return IntStream.of(numbers).average().orElse(0d);
}
}
Next, we’ll provide some numbers to our average calculator thread and validate the output:
@Test
public void whenSendingParameterToCallable_thenSuccessful() throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Double> result = executorService.submit(new AverageCalculator(1, 2, 3));
try {
assertEquals(2.0, result.get().doubleValue());
} finally {
executorService.shutdown();
}
}
Note that the reason this works is that we’ve handed our class its state before launching the thread.
4. Sending Parameters Through a Closure
Another way of passing parameters to a thread is by creating a closure.
A closure is a scope that can inherit some of its parent’s scope – we see it with lambdas and anonymous inner classes.
Let’s extend our previous example and create two threads.
The first one will calculate the average:
executorService.submit(() -> IntStream.of(numbers).average().orElse(0d));
And, the second will do the sum:
executorService.submit(() -> IntStream.of(numbers).sum());
Let’s see how we can pass the same parameter to both threads and get the result:
@Test
public void whenParametersToThreadWithLamda_thenParametersPassedCorrectly()
throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(2);
int[] numbers = new int[] { 4, 5, 6 };
try {
Future<Integer> sumResult =
executorService.submit(() -> IntStream.of(numbers).sum());
Future<Double> averageResult =
executorService.submit(() -> IntStream.of(numbers).average().orElse(0d));
assertEquals(Integer.valueOf(15), sumResult.get());
assertEquals(Double.valueOf(5.0), averageResult.get());
} finally {
executorService.shutdown();
}
}
One important thing to remember is to keep the parameters effectively final or we won’t be able to hand them to the closure.
Also, the same concurrency rules apply here as everywhere. If we change a value in the numbers array while the threads are running, there is no guarantee that they will see it without introducing some synchronization.
And to wrap up here, an anonymous inner class would have worked, too, say if we are using an older version of Java:
final int[] numbers = { 1, 2, 3 };
Thread parameterizedThread = new Thread(new Callable<Double>() {
@Override
public Double call() {
return calculateTheAverage(numbers);
}
});
parameterizedThread.start();
5. Conclusion
In this article, we discovered the different options available for passing parameters to a Java thread.
As always, the code samples are available over on Github.