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

Параллельные операции над массивами

В JDK 8 к классу Arrays был добавлен ряд методов, которые позволяют в параллельном режиме совершать обработку элементов массива. И хотя данные методы формально не входят в Stream API, но реализуют схожую функциональность, что и параллельные потоки:

  • parallelPrefix(): вычисляет некоторое значение для элементов массива (например, сумму элементов).
  • parallelSetAll(): устанавливает элементы массива с помощью лямбда-выражения.
  • parallelSort(): сортирует массив.

Используем метод parallelSetAll() для установки элементов массива:

import java.util.Arrays;

public class Program {
public static void main(String[] args) {
int[] numbers = initializeArray(6);
for (int i : numbers)
System.out.println(i);
}

public static int[] initializeArray(int size) {
int[] values = new int[size];
Arrays.parallelSetAll(values, i -> i * 10);
return values;
}
}

В метод Arrays.parallelSetAll передаются два параметра: изменяемый массив и функция, которая устанавливает элементы массива. Эта функция перебирает все элементы и в качестве параметра получает индекс текущего перебираемого элемента. Выражение i -> i * 10 означает, что по каждому индексу в массиве будет храниться число, равное i * 10. В итоге мы получим следующий вывод:

0
10
20
30
40
50

Рассмотрим более сложный пример. Пусть у нас есть следующий класс Phone:

class Phone {
private String name;
private int price;

public Phone(String name, int price) {
this.name = name;
this.price = price;
}

public String getName() {
return name;
}

public void setName(String val) {
this.name = val;
}

public int getPrice() {
return price;
}

public void setPrice(int val) {
this.price = val;
}
}

Теперь произведём манипуляции с массивом объектов Phone:

Phone[] phones = new Phone[] {
new Phone("iPhone 8", 54000),
new Phone("Pixel 2", 45000),
new Phone("Samsung Galaxy S9", 40000),
new Phone("Nokia 9", 32000)
};

Arrays.parallelSetAll(phones, i -> {
phones[i].setPrice(phones[i].getPrice() - 10000);
return phones[i];
});

for (Phone p : phones)
System.out.printf("%s - %d \n", p.getName(), p.getPrice());

Теперь лямбда-выражение в методе Arrays.parallelSetAll представляет блок кода. И так как лямбда-выражение должно возвращать объект, то нам надо явным образом использовать оператор return. В этом лямбда-выражении опять же функция получает индексы перебираемых элементов, и по этим индексам мы можем обратиться к элементам массива и их изменить. Конкретно в данном случае происходит уменьшение цены смартфонов на 10000 единиц. В итоге мы получим следующий консольный вывод:

iPhone 8 - 44000
Pixel 2 - 35000
Samsung Galaxy S9 - 30000
Nokia 9 - 22000

Сортировка​

Отсортируем массив чисел в параллельном режиме:

int[] nums = {30, -4, 5, 29, 7, -8};
Arrays.parallelSort(nums);

for (int i : nums)
System.out.println(i);

Метод Arrays.parallelSort() в качестве параметра принимает массив и сортирует его по возрастанию:

-8
-4
5
7
29
30

Если же нам надо как-то по-другому отсортировать объекты, например, по модулю числа, или у нас более сложные объекты, то мы можем создать свой компаратор и передать его в качестве второго параметра в Arrays.parallelSort(). Например, возьмём выше определенный класс Phone и создадим для него компаратор:

import java.util.Arrays;
import java.util.Comparator;

public class Program {
public static void main(String[] args) {
Phone[] phones = new Phone[] {
new Phone("iPhone 8", 54000),
new Phone("Pixel 2", 45000),
new Phone("Samsung Galaxy S9", 40000),
new Phone("Nokia 9", 32000)
};

Arrays.parallelSort(phones, new PhoneComparator());

for (Phone p : phones)
System.out.println(p.getName());
}
}

class PhoneComparator implements Comparator<Phone> {
public int compare(Phone a, Phone b) {
return a.getName().toUpperCase().compareTo(b.getName().toUpperCase());
}
}

Метод parallelPrefix​

Метод parallelPrefix() подходит для тех случаев, когда надо получить элемент массива или объект того же типа, что и элементы массива, который обладает некоторыми признаками. Например, в массиве чисел это может быть максимальное, минимальное значения и т.д. Например, найдем произведение чисел:

int[] numbers = {1, 2, 3, 4, 5, 6};
Arrays.parallelPrefix(numbers, (x, y) -> x * y);

for (int i: numbers)
System.out.println(i);

Мы получим следующий результат:

1
2
6
24
120
720

То есть, как мы видим из консольного вывода, лямбда-выражение из Arrays.parallelPrefix, которое представляет бинарную функцию, получает два элемента и выполняет над ними операцию. Результат операции сохраняется и передаётся в следующий вызов бинарной функции.