Concurrent programming in Java8. Creating multi-threaded programs using the Fork / Join Framework

Published on November 16, 2015

Concurrent programming in Java8. Creating multi-threaded programs using the Fork / Join Framework

The article is devoted to such an interesting and useful mechanism (sets of mechanisms and libraries) as the Fork / Join Framework . It allows you to speed up calculations many times, to achieve maximum results during processing, using all available system capabilities (processors).

In this article, classes will be created using the Fork / Join Framework . The code shows one of the possible uses for parallel programming. So, let's begin.

When creating applications, you should maximally separate the parts responsible for launching, configuring and processing data. And this option of working with Fork / Join is no exception. The examples will use the classes Start, Stream, Calc, respectively.

Part One - Launch


For testing, we will create the Start class, it will serve as the “point” of launch. The value timebetweenStartEnd will show us the time interval between the beginning and the end of calculations. By calculations is meant raising to a power of numbers from 0 to 1,000,000 in two versions in single-threaded and multi-threaded modes.

The Start class defines the thread pool ForkJoinPool () . Using the invoke () method , the result of starting the task and waiting for its completion was achieved. The value of componentValue is defined as 1,000,000. The newly created instance of the Stream class defines the source data. Using invoke () we “translate” this task to execution.

import java.util.concurrent.ForkJoinPool;
public class Start {
    public static void main(String[] args) {
     final int componentValue = 1000000;
        Long beginT = System.nanoTime();
        ForkJoinPool fjp = new ForkJoinPool();
        Stream test = new Stream(componentValue,0,componentValue);
        fjp.invoke(test);
        Long endT = System.nanoTime();
        Long timebetweenStartEnd = endT - beginT;
        System.out.println("=====time========" +timebetweenStartEnd);
    }
}

Part two. Customization. Class Stream


The second part of the mechanism is the class (Stream), which is responsible for configuring multithreading. Now we have only two such options: the first - according to the number of processed values ​​in one thread (hereinafter - the "cutoff"), the second - according to the number of processors (obtained using the availableProcessors () method ). I ask readers to pay attention that in this article the mechanism for dynamically creating threads will not be worked out depending on the number of processors and / or other conditions. This is the topic of the next article.

The class uses the abstract compute () method , which is responsible for starting calculations, in our case, it is choosing a calculation option and starting calculations in the go method of the Calc class. Using the invokeAll () method, start the subtasks.

It can be seen from the algorithm that if we have more than one processor, or the cut-off value (500,000) is greater than / equal to the parts received, then the calculation takes place. In the example, we divide forSplit into several parts (two) and run two subtasks. Changing the value of the variable countLimit or setting the value of countProcessors equal to one, only one data processing task will be launched.

import java.util.concurrent.RecursiveAction;
public class Stream extends RecursiveAction {
    final int countProcessors = Runtime.getRuntime().availableProcessors();
    final int countLimit = 500000;
    int start;
    int end;
    int forSplit;
    Stream(int componentValue,int startNumber, int endNumber) {
        forSplit = componentValue;
        start = startNumber;
        end = endNumber;
    }
    protected void compute() {
        if (countProcessors == 1 || end - start <= countLimit) {
            System.out.println("=run=");
            for(int i = start; i <= end; i++) {
                new Calc().go(i);
            }
        } else {
            int middle = (start + end)/ 2;
            invokeAll(new Stream(forSplit, 0, middle),
                    new Stream(forSplit, middle+1, end));
        }
    }
}

Part three. Execution of the calculation. Class calc


This class is responsible for raising a number to a power. The following part is intended for demonstration and may contain any calculations from enumerating collections to writing data to the repository.

public class Calc {
    public void go(int numberForCalc) {
        for(int i = 0; i <= numberForCalc; i++) {
            double pow = Math.pow(numberForCalc,100);
        }
    }
}

Instead of ending


This material will be useful to those who have just started to learn parallel programming. It shows the basics of working with a small part of the functionality. I draw the attention of readers that for small calculations, the time taken to create the second subtask may be longer than the calculation time. In the following articles, we will approach the creation of flexible functionality for launching and determining the maximum possible parallel flows, as well as touch on the topic of limitations associated with simultaneously executed commands.