Расскажите о параллельной обработке в Java 8

Стримы могут быть последовательными и параллельными. Операции над последовательными стримами выполняются в одном потоке процессора, над параллельными — используя несколько потоков процессора. Параллельные стримы используют общий ForkJoinPool доступный через статический ForkJoinPool.commonPool() метод. При этом, если окружение не является многоядерным, то поток будет выполняться как последовательный. Фактически применение параллельных стримов сводится к тому, что данные в стримах будут разделены на части, каждая часть обрабатывается на отдельном ядре процессора, и в конце эти части соединяются, и над ними выполняются конечные операции.

Для создания параллельного потока из коллекции можно также использовать метод parallelStream() интерфейса Collection.

Чтобы сделать обычный последовательный стрим параллельным, надо вызвать у объекта Stream метод parallel(). Метод isParallel() позволяет узнать является ли стрим параллельным.

С помощью, методов parallel() и sequential() можно определять какие операции могут быть параллельными, а какие только последовательными. Также из любого последовательного стрима можно сделать параллельный и наоборот:

collection

.stream()

.peek(...) // операция последовательна

.parallel()

.map(...) // операция может выполняться параллельно,

.sequential()

.reduce(...) // операция снова последовательна

Как правило, элементы передаются в стрим в том же порядке, в котором они определены в источнике данных. При работе с параллельными стримами система сохраняет порядок следования элементов. Исключение составляет метод forEach(), который может выводить элементы в произвольном порядке. И чтобы сохранить порядок следования, необходимо применять метод forEachOrdered().

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

Размер данных - чем больше данных, тем сложнее сначала разделять данные, а потом их соединять.

Количество ядер процессора. Теоретически, чем больше ядер в компьютере, тем быстрее программа будет работать. Если на машине одно ядро, нет смысла применять параллельные потоки.

Чем проще структура данных, с которой работает поток, тем быстрее будут происходить операции. Например, данные из ArrayList легко использовать, так как структура данной коллекции предполагает последовательность несвязанных данных. А вот коллекция типа LinkedList - не лучший вариант, так как в последовательном списке все элементы связаны с предыдущими/последующими. И такие данные трудно распараллелить.

Над данными примитивных типов операции будут производиться быстрее, чем над объектами классов.

Крайне не рекомендуется использовать параллельные стримы для сколько-нибудь долгих операций (например сетевых соединений), так как все параллельные стримы работают c одним ForkJoinPool, то такие долгие операции могут остановить работу всех параллельных стримов в JVM из-за отсутствия доступных потоков в пуле, т.е. параллельные стримы стоит использовать лишь для коротких операций, где счет идет на миллисекунды, но не для тех где счет может идти на секунды и минуты;

Сохранение порядка в параллельных стримах увеличивает издержки при выполнении и если порядок не важен, то имеется возможность отключить его сохранение и тем самым увеличить производительность, использовав промежуточную операцию unordered():

collection.parallelStream()

.sorted()

.unordered()

.collect(Collectors.toList());

 

Какие конечные методы работы со стримами вы знаете?

● findFirst() возвращает первый элемент;

● findAny() возвращает любой подходящий элемент;

● collect() представление результатов в виде коллекций и других структур данных;

● count() возвращает количество элементов;

● anyMatch() возвращает true, если условие выполняется хотя бы для одного элемента;

● noneMatch() возвращает true, если условие не выполняется ни для одного элемента;

● allMatch() возвращает true, если условие выполняется для всех элементов;

● min() возвращает минимальный элемент, используя в качестве условия Comparator;

● max() возвращает максимальный элемент, используя в качестве условия Comparator;

● forEach() применяет функцию к каждому объекту (порядок при параллельном выполнении не гарантируется);

● forEachOrdered() применяет функцию к каждому объекту с сохранением порядка элементов;

● toArray() возвращает массив значений;

● reduce()позволяет выполнять агрегатные функции и возвращать один результат.

● Для числовых стримов дополнительно доступны:

● sum() возвращает сумму всех чисел;

● average() возвращает среднее арифметическое всех чисел.

 

Какие промежуточные методы работы со стримами вы знаете?

● filter() отфильтровывает записи, возвращая только записи, соответствующие условию;

● skip() позволяет пропустить определенное количество элементов в начале;

● distinct() возвращает стрим без дубликатов (для метода equals());

● map() преобразует каждый элемент;

● peek() возвращает тот же стрим, применяя к каждому элементу функцию;

● limit() позволяет ограничить выборку определенным количеством первых элементов;

● sorted() позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator;

● mapToInt(), mapToDouble(), mapToLong() - аналоги map() возвращающие стрим числовых примитивов;

● flatMap(), flatMapToInt(), flatMapToDouble(), flatMapToLong() - похожи на map(), но могут создавать из одного элемента несколько.

Для числовых стримов дополнительно доступен метод mapToObj(), который преобразует числовой стрим обратно в объектный.

 

Как вывести на экран 10 случайных чисел, используя forEach()?

(new Random())

.ints()

.limit(10)

.forEach(System.out::println);

 

Как можно вывести на экран уникальные квадраты чисел используя метод map()?

Stream

.of(1, 2, 3, 2, 1)

.map(s -> s * s)

.distinct()

.forEach(System.out::println);

 

Как вывести на экран количество пустых строк с помощью метода filter()?

System.out.println(

Stream

  .of("Hello", "", ", ", "world", "!")

  .filter(String::isEmpty)

  .count());

 


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: