1. Обзор
В этом руководстве мы рассмотрим различные варианты передачи параметров в поток Java .
2. Основы работы с нитью
Напомню , что мы можем создать поток в Java, [реализуя `Runnable`](/lessons/b/-java-runnable-vs-extending-thread)
или Callable
.
Чтобы запустить поток, мы можем вызвать Thread#start
(путем передачи экземпляра Runnable
) или использовать пул потоков , отправив его в ExecutorService .
Однако ни один из этих подходов не принимает никаких дополнительных параметров.
Давайте посмотрим, что мы можем сделать, чтобы передать параметры потоку.
3. Отправка параметров в конструкторе
Первый способ, которым мы можем отправить параметр потоку, — это просто передать его нашему Runnable
или Callable
в их конструкторе.
Давайте создадим AverageCalculator
, который принимает массив чисел и возвращает их среднее значение:
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);
}
}
Затем мы предоставим некоторые числа нашему потоку среднего калькулятора и проверим вывод:
@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();
}
}
Обратите внимание, что причина, по которой это работает, заключается в том, что мы передали нашему классу его состояние перед запуском потока.
4. Отправка параметров через замыкание
Другой способ передачи параметров потоку — создание замыкания .
Замыкание
— это область, которая может наследовать часть области своего родителя — мы видим это с лямбда-выражениями и анонимными внутренними классами.
Давайте расширим наш предыдущий пример и создадим два потока.
Первый вычислит среднее значение:
executorService.submit(() -> IntStream.of(numbers).average().orElse(0d));
А второй сделает сумму:
executorService.submit(() -> IntStream.of(numbers).sum());
Давайте посмотрим, как мы можем передать один и тот же параметр обоим потокам и получить результат:
@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();
}
}
Важно помнить одну важную вещь: параметры должны быть окончательными , иначе мы не сможем передать их замыканию.
Кроме того, здесь действуют те же правила параллелизма, что и везде. Если мы изменим значение в массиве чисел
во время работы потоков, нет гарантии, что они увидят его без введения некоторой синхронизации .
И в заключение, анонимный внутренний класс тоже сработал бы, скажем, если бы мы использовали более старую версию Java:
final int[] numbers = { 1, 2, 3 };
Thread parameterizedThread = new Thread(new Callable<Double>() {
@Override
public Double call() {
return calculateTheAverage(numbers);
}
});
parameterizedThread.start();
5. Вывод
В этой статье мы обнаружили различные варианты передачи параметров в поток Java.
Как всегда, образцы кода доступны на Github .