Métodos Paralelos em Arrays em Java
Introduzidos no JDK 8, a classe java.util.Arrays
foi enriquecida com um conjunto de métodos que permitem o processamento de elementos de um array em paralelo. Esses métodos são especialmente úteis quando se trabalha com arrays grandes e a performance é crucial, como em operações de ordenação ou cálculos complexos. Diferentemente dos streams, eles atuam diretamente sobre os dados, evitando etapas intermediárias e aproveitando múltiplos núcleos de processamento para acelerar a execução.
Os principais métodos são:
parallelSetAll()
: Preenche um array usando uma função geradora.parallelSort()
: Ordena um array em paralelo.parallelPrefix()
: Realiza um cálculo cumulativo.
Inicializando um Array com parallelSetAll()
O método parallelSetAll()
é ideal para inicializar ou modificar todos os elementos de um array, onde o valor de cada elemento depende de sua posição (índice).
import java.util.Arrays;
public class ParallelSetAllExample {
public static void main(String[] args) {
int[] numbers = new int[6];
// A função lambda recebe o índice 'i' e define o valor para numbers[i]
Arrays.parallelSetAll(numbers, i -> i * 10);
System.out.println(Arrays.toString(numbers)); // [0, 10, 20, 30, 40, 50]
}
}
Cuidado com a Mutabilidade em parallelSetAll
Ao trabalhar com arrays de objetos, é crucial evitar a modificação direta do estado dos objetos dentro da lambda, pois isso pode levar a condições de corrida em um ambiente paralelo. A abordagem mais segura é criar novas instâncias imutáveis.
import java.util.Arrays;
record Phone(String name, int price) {
// Retorna uma nova instância com o preço alterado (abordagem imutável)
public Phone withPrice(int newPrice) {
return new Phone(this.name, newPrice);
}
}
public class ParallelSetAllObjects {
public static void main(String[] args) {
Phone[] phones = {
new Phone("iPhone 15", 54000),
new Phone("Pixel 8", 45000),
new Phone("Samsung Galaxy S24", 40000)
};
// A função deve retornar o objeto que será colocado no array
Arrays.parallelSetAll(phones, i -> {
int currentPrice = phones[i].price();
return phones[i].withPrice(currentPrice - 10000);
});
for(Phone p: phones) {
System.out.printf("%s - %d\n", p.name(), p.price());
}
}
}
O resultado será:
iPhone 15 - 44000 Pixel 8 - 35000 Samsung Galaxy S24 - 30000
Ordenando um Array com parallelSort()
O método parallelSort()
funciona como o Arrays.sort()
, mas divide o trabalho de ordenação entre múltiplos núcleos, o que pode ser mais rápido para arrays grandes (geralmente com dezenas de milhares de elementos ou mais).
Quando precisarmos de uma lógica de ordenação customizada (por exemplo, ordenar objetos complexos), podemos usar a versão sobrecarregada do método que aceita um Comparator
como segundo argumento.
Vamos usar a classe Phone
e criar um Comparator
para ordenar os telefones pelo nome.
import java.util.Arrays;
import java.util.Comparator;
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 int getPrice() {
return price;
}
}
class PhoneComparator implements Comparator<Phone> {
public int compare(Phone a, Phone b) {
// Usa compareToIgnoreCase para uma ordenação alfabética que ignora maiúsculas/minúsculas
return a.getName().compareToIgnoreCase(b.getName());
}
}
public class ParallelSortCustomExample {
public static void main(String[] args) {
Phone[] phones = new Phone[]{
new Phone("iPhone 15", 54000),
new Phone("Pixel 8", 45000),
new Phone("Samsung Galaxy S24", 40000),
new Phone("Nokia G42", 32000)
};
Arrays.parallelSort(phones, new PhoneComparator());
for(Phone p: phones) {
System.out.println(p.getName());
}
}
}
Saída:
iPhone 15 Nokia G42 Pixel 8 Samsung Galaxy S24
Cálculos Cumulativos com parallelPrefix()
O método parallelPrefix()
é usado para aplicar uma operação cumulativa a um array. Em cada posição i
, o novo valor será o resultado da operação entre o valor acumulado em i-1
e o valor original em i
. É o análogo de "running total" ou "soma de prefixos".
Por exemplo, para calcular o produto cumulativo de uma sequência de números:
import java.util.Arrays;
public class ParallelPrefixExample {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5, 6};
// A função (x, y) -> x * y é aplicada cumulativamente
Arrays.parallelPrefix(numbers, (x, y) -> x * y);
System.out.println(Arrays.toString(numbers)); // [1, 2, 6, 24, 120, 720]
}
}
Para entender o que acontece, veja o passo a passo:
Índice | Valor Original | Acumulado Anterior | Operação | Novo Valor no Array |
---|---|---|---|---|
0 | 1 | - (nenhum) | - | 1 |
1 | 2 | 1 (do índice 0) | 1 * 2 | 2 |
2 | 3 | 2 (do índice 1) | 2 * 3 | 6 |
3 | 4 | 6 (do índice 2) | 6 * 4 | 24 |
4 | 5 | 24 (do índice 3) | 24 * 5 | 120 |
5 | 6 | 120 (do índice 4) | 120 * 6 | 720 |
Resumo
- A classe
Arrays
oferece métodos paralelos (parallelSetAll
,parallelSort
,parallelPrefix
) que são alternativas eficientes aos streams para operações em arrays. parallelSetAll(array, generator)
: Preenche um array, onde o valor de cada elemento é gerado por uma função que recebe seu índice.parallelSort(array)
: Ordena um array em paralelo, sendo mais rápido para grandes volumes de dados. Aceita umComparator
para ordenação customizada.parallelPrefix(array, operator)
: Modifica o array para que cada elemento contenha o resultado cumulativo de uma operação binária.