0Pricing
Java Academy · Урок

Когда параллелизм помогает

Учитывайте нагрузку и размер данных

«Когда параллелизм помогает» — бесплатный урок Java Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.

Параллелизм имеет цену

Переход к параллельному выполнению добавляет накладные расходы: разделение данных, распределение задач и объединение результатов. Он окупается только тогда, когда эти расходы меньше сэкономленного времени.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long sum = IntStream.rangeClosed(1, 10_000_000)
            .parallel()
            .asLongStream()
            .sum();
        System.out.println(sum);
    }
}

Фактор 1: размер данных (N)

Большое значение N позволяет распределить фиксированные накладные расходы параллелизма на большее число элементов. Приблизительное практическое правило: параллельное выполнение начинает окупаться при десятках тысяч элементов.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long count = IntStream.rangeClosed(1, 5_000_000)
            .parallel()
            .filter(n -> n % 7 == 0)
            .count();
        System.out.println(count);
    }
}

Фактор 2: объём работы на элемент (Q)

Важна стоимость Q обработки каждого элемента. Большой объём работы на элемент (сложные вычисления) приносит пользу от параллелизма даже при меньшем N.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long primes = IntStream.rangeClosed(2, 200_000)
            .parallel()
            .filter(Main::isPrime)
            .count();
        System.out.println(primes);
    }

    static boolean isPrime(int n) {
        for (int i = 2; (long) i * i <= n; i++)
            if (n % i == 0) return false;
        return true;
    }
}

Интуитивное понимание произведения N на Q

Представляйте общий объём полезной работы как N x Q. Чем больше это произведение, тем сильнее может помочь параллелизм. Малое N или тривиальное Q редко оправдывают его использование.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        double sum = IntStream.rangeClosed(1, 1_000_000)
            .parallel()
            .mapToDouble(n -> Math.sqrt(n) * Math.log(n + 1))
            .sum();
        System.out.println(sum);
    }
}

Фактор 3: возможность разделения

Источники данных, которые легко и равномерно разделяются, хорошо подходят для параллельной обработки: массивы, ArrayList и IntStream.range. LinkedList и источники на основе итераторов разделяются плохо.

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1_000_000; i++) list.add(i);
        long even = list.parallelStream().filter(n -> n % 2 == 0).count();
        System.out.println(even);
    }
}

Хороший источник: массивы и диапазоны

Диапазоны примитивных значений имеют известный размер и разделяются за O(1), поэтому идеально подходят как параллельные источники.

import java.util.stream.LongStream;

public class Main {
    public static void main(String[] args) {
        long sum = LongStream.rangeClosed(1, 20_000_000)
            .parallel()
            .sum();
        System.out.println(sum);
    }
}

Плохой источник: итеративные генераторы

Stream.iterate по определению создаёт элементы последовательно, поэтому не может разделиться, пока элементы не будут материализованы. Это слабый источник для параллельной обработки.

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        long count = Stream.iterate(1, n -> n + 1)
            .limit(1_000_000)
            .parallel()
            .filter(n -> n % 2 == 0)
            .count();
        System.out.println(count);
    }
}

Фактор 4: дешёвый этап объединения

Параллелизму нужен недорогой способ объединять частичные результаты. sum и count объединяются тривиально, а создание отсортированного списка или сборщика со сложной древовидной структурой требует больших затрат.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        int max = IntStream.rangeClosed(1, 5_000_000)
            .parallel()
            .map(n -> n % 1000)
            .max()
            .getAsInt();
        System.out.println(max);
    }
}

Избегайте задач, ограниченных вводом-выводом

Параллельные потоки предназначены для задач, ограниченных производительностью CPU, и выполняются в пуле fork-join. Блокирующий ввод-вывод истощает общий пул и вредит всему приложению. Вместо этого используйте отдельные исполнители для операций ввода-вывода.

import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long total = IntStream.rangeClosed(1, 1_000_000)
            .parallel()
            .mapToLong(n -> (long) n * n)
            .sum();
        System.out.println(total);
    }
}

Измеряйте, а не предполагайте

Единственный надёжный способ узнать, помогает ли параллелизм, — измерить производительность на реалистичных данных. Интуитивные представления о производительности часто ошибочны.

import java.util.stream.LongStream;

public class Main {
    public static void main(String[] args) {
        long start = System.nanoTime();
        long sum = LongStream.rangeClosed(1, 50_000_000).parallel().sum();
        long ms = (System.nanoTime() - start) / 1_000_000;
        System.out.println("sum=" + sum + " took ~" + ms + "ms");
    }
}

Практический список проверок

Выбирайте параллельное выполнение, если выполняются все условия:

  • Большое N и/или большой объём работы на элемент.
  • Источник, допускающий разделение (массив, ArrayList, диапазон).
  • Дешёвое ассоциативное объединение.
  • Задача ограничена CPU и не содержит блокирующего ввода-вывода.
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        double avg = IntStream.rangeClosed(1, 10_000_000)
            .parallel()
            .mapToDouble(Math::sqrt)
            .average()
            .getAsDouble();
        System.out.println(avg);
    }
}

Быстрая проверка

Какой сценарий лучше всего подходит для параллельного потока?

Итоги

Вы узнали, когда параллелизм приносит пользу:

  • Польза растёт вместе с N x Q (размером данных, умноженным на стоимость обработки одного элемента).
  • Нужен источник, допускающий разделение (массивы, ArrayList, диапазоны).
  • Нужно дешёвое ассоциативное объединение.
  • Задача должна быть ограничена CPU; избегайте блокирующего ввода-вывода и всегда проводите измерения.

Часто задаваемые вопросы

Урок «Когда параллелизм помогает» бесплатный?

Да — полный текст урока «Когда параллелизм помогает» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.

Чему я научусь в уроке «Когда параллелизм помогает»?

Учитывайте нагрузку и размер данных Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Java Academy?

Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Когда параллелизм помогает»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Java Academy?

Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Создание параллельных потоков
  2. Когда параллелизм помогает
  3. Потокобезопасность и побочные эффекты
  4. Распространённые ошибки
← Назад к Java Academy