Статьи

Введение в Fork / Join Framework

В предыдущих статьях о фреймворке java.util.concurrent я представил, что для меня является ядром пакета и что вы будете использовать чаще всего в реальных приложениях.

Если идти дальше с этой темой, то стоит знать о новой фреймворке fork / join, представленном в Java 7. Плюсы и минусы этой фреймворки по сравнению с использованием пулов потоков или обычной разработки потоков / выполнимых приложений были предметом многих дискуссий, но Я думаю, что это все еще хороший инструмент в вашем арсенале.

Я буду использовать более классический подход, чтобы показать вам базовую структуру структуры fork / join и использовать ее с помощью кода алгоритма Quicksort .

Моя цель — познакомить вас с инструментами, предоставляемыми структурой fork / join в Java 7, это не обсуждение лучшей реализации Quicksort. Если вы хотите вникнуть в лучшее из возможного Quicksort, я рекомендую вам прочитать этот замечательный пост об этом.

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

Реализация вашего решения проблем должна быть подклассом ForkJoinTask , и вы, скорее всего, будете реализовывать подкласс его дочерних объектов RecursiveAction или RecursiveTask ; RecursiveAction представляет рекурсивное решение проблем, способное непосредственно решать достаточно малую проблему или разбивать ее на более мелкие рабочие блоки; RecursiveTask похож, но он также имеет возможность возвращать результат для каждого выполненного рабочего блока.

Для реализации нашей быстрой сортировки мы будем использовать стратегию на месте (мы хардкорные, верно?) И использовать общий список в качестве данных, чтобы он поддерживал любую Java Comparable. С этими предпосылками наша задача может быть закодирована как:

package com.ricardozuasti;
 
import java.util.List;
import java.util.concurrent.RecursiveAction;
 
public class QuickSort<T extends Comparable> extends RecursiveAction {
 
    private List<T> data;
    private int left;
    private int right;
 
    public QuickSort(List<T> data){
        this.data=data;
        this.left = 0;
        this.right = data.size() - 1;
    }
 
    public QuickSort(List<T> data, int left, int right){
        this.data = data;
        this.left = left;
        this.right = right;
    }
 
    @Override
    protected void compute() {
        if (left < right){
            int pivotIndex = left + ((right - left)/2);
 
            pivotIndex = partition(pivotIndex);
 
            invokeAll(new QuickSort(data, left, pivotIndex-1),
                      new QuickSort(data, pivotIndex+1, right));
        }
 
    }
 
    private int partition(int pivotIndex){
        T pivotValue = data.get(pivotIndex);
 
        swap(pivotIndex, right);
 
        int storeIndex = left;
        for (int i=left; i<right; i++){
            if (data.get(i).compareTo(pivotValue) < 0){
                swap(i, storeIndex);
                storeIndex++;
            }
        }
 
        swap(storeIndex, right);
 
        return storeIndex;
    }
 
    private void swap(int i, int j){
        if (i != j){
            T iValue = data.get(i);
 
            data.set(i, data.get(j));
            data.set(j, iValue);
        }
    }
}

Метод compute () — это метод, который будет вызывать инфраструктура fork / join для каждого рабочего модуля. Наш базовый шаг (небольшой рабочий блок) — ничего не делать (так как список размера 1 всегда сортируется). Наш рекурсивный шаг использует метод invokeAll () , который является ярлыком для fork () всех небольших рабочих модулей, а затем снова присоединяет их к ним () .

Вот и все! Нам больше ничего не нужно. Чтобы фактически использовать наш новый параллельный Quicksort, мы используем экземпляр ForkJoinPool :

package com.ricardozuasti;
 
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
 
public class Concurrency3 {
    public static void main(String[] args) {
        final int SIZE = 10000;
 
        List<Integer> myList = new ArrayList<Integer>(SIZE);
 
        for (int i=0; i<SIZE; i++){
            int value = (int) (Math.random() * 100);
            myList.add(value);
        }
 
        QuickSort<Integer> quickSort = new QuickSort<Integer>(myList);
 
        ForkJoinPool pool = new ForkJoinPool();
        pool.invoke(quickSort);
    }
}

И снова метод invoke эквивалентен выполнению fork (), за которым следует join () , поэтому после возврата метода pool.invoke () наш список сортируется.