Перейти к основному содержимому

Передача параметров в потоки Java

· 3 мин. чтения

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 .