How to use Futuretask class correctly?

Asked

Viewed 179 times

1

I’m looking to calculate the number of numbers that can be divided by a splitter with no rest in parallel.

The problem is the result of the method always return to half of what it should, it seems that only one thread is being executed. Looking at the ArrayList from Futuretask I really saw that one is with the value it should after the calculation and the other is with zero.

Why is this happening?

  public static int calcularQuantidadeDivisivel(final int first, final int last, final int divisor)
        throws InterruptedException, ExecutionException {

    int amount = 0;
    int threadNum = 2;
    ExecutorService executor = Executors.newFixedThreadPool(threadNum);
    List<FutureTask<Integer>> taskList = new ArrayList<FutureTask<Integer>>();

    int quantidadeFatiasPorThread =  (last / threadNum);

    System.out.println("Quantidade: "+quantidadeFatiasPorThread);
    primeiro =0; ultimo = primeiro+quantidadeFatiasPorThread;

    arrayFutureTasks = new FutureTask[threadNum];
    for(int i = 0; i< threadNum;i++){
        System.out.println(i+" "+threadNum);
         System.out.println("primeiro :"+primeiro+" ultimo: "+ultimo);
        arrayFutureTasks[i] = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() {
                return FutureTaskDemo.calcularQuantidadeDivisivelSerial(primeiro, ultimo, divisor);
            }
        });

        taskList.add(arrayFutureTasks[i]);
        executor.execute(arrayFutureTasks[i]);

        primeiro = ultimo+1;
        ultimo +=quantidadeFatiasPorThread;

        if(ultimo > last){

            int quantidadeMais = ultimo - last;
            ultimo = ultimo - quantidadeMais;

        }
    }

    for (int j = 0; j < threadNum; j++) {

        FutureTask<Integer> futureTask = taskList.get(j);
        amount += futureTask.get();

    }

    executor.shutdown();

    return amount;
}

1 answer

3


First, your question suffers from some problems:

  • Your code has several compilation errors whose correction is not trivial. First, the variables primeiro and ultimo were not declared, and due to the modifier final in the parameters, also can not be just a simple case of having forgotten to rename the first and the last, even more given the fact that you clearly use ultimo and last as two distinct variables.
  • You also did not provide the method code calcularQuantidadeDivisivelSerial, making it difficult for us to see what is happening, even more so if there can be some problem within this method.
  • The variable arrayFutureTasks has also not been declared and clearly is not the same thing as taskList. And that’s another reason why the code doesn’t compile.
  • You have not reported what values you used in the function to get the result you got, and without that it is difficult to answer your question "Looking at the Futuretask arrayList I really saw that one is with the value it should after the calculation and the other is with zero, but why is this happening?".

These above problems are reason enough to close your question as "it’s not clear what you’re asking", but I’ll try to answer anyway.


First let’s fix the compilation problems:

  1. I’ll assume the class name is FutureTaskDemo and put the imports necessary.

  2. The method calcularQuantidadeDivisivelSerial:

    public static int calcularQuantidadeDivisivelSerial(int first, int last, int divisor) {
        int result = 0;
        for (int i = first; i <= last; i++) {
            if (i % divisor == 0) result++;
        }
        return result;
    }
  1. I will assume that primeiro and ultimo should be declared the first time a value is assigned to them. So this:
primeiro =0; ultimo = primeiro+quantidadeFatiasPorThread;

Becomes that:

int primeiro = 0;
int ultimo = primeiro + quantidadeFatiasPorThread;
  1. Since primeiro and ultimo are not variable final (and not effectively-final, once they mutate), the compiler will complain that these variables are being used within the code of the anonymous class. So I’m gonna turn it:
        arrayFutureTasks[i] = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() {
                return FutureTaskDemo.calcularQuantidadeDivisivelSerial(primeiro, ultimo, divisor);
            }
        });

Therein:

        int primeiroFinal = primeiro;
        int ultimoFinal = ultimo;
        arrayFutureTasks[i] = new FutureTask<>(() -> calcularQuantidadeDivisivelSerial(primeiroFinal, ultimoFinal, divisor));

And I’ve already used lambda-Xpression instead of anonymous class up there.

  1. The variable arrayFutureTasks has not been declared. So I will change that:
    arrayFutureTasks = new FutureTask[threadNum];

That’s why:

    @SuppressWarnings("unchecked")
    FutureTask<Integer>[] arrayFutureTasks = (FutureTask<Integer>[]) new FutureTask<?>[threadNum];

Now your code must be compilable, but showing incorrect results. There is nothing wrong with it regarding the threads or the FutureTask. Your problem is that your code has a number of mathematical errors.

Let’s try to pack it up:

  1. The parameter first is not used anywhere. I assume you wanted it to be used to calculate the values of the first slice to be computed, otherwise the code will produce an incorrect result if the first number of the track is not zero. So that:
    int primeiro = 0;
    int ultimo = primeiro + quantidadeFatiasPorThread;

Becomes that:

    int primeiro = first;
    int ultimo = primeiro + quantidadeFatiasPorThread;
  1. The size of the first slice is wrong. The reason for this is the way the variable ultimo has its first value computed. For example, if primeiro is equal to 5 and quantidadeFatiasPorThread is equal to 4, then we will have to:

last = first + quantityFatialsPorThread = 5 + 4 = 9

And so the first slice has the elements [5, 6, 7, 8, 9], ie 5 elements. However the fact that the slice has 5 elements is contrary to the purpose of the variable quantidadeFatiasPorThread that has as value 4. Here is the solution:

    int primeiro = first;
    int ultimo = primeiro + quantidadeFatiasPorThread - 1;
  1. Your slice size calculation has problems. The first is that as you do not use the first in it, and therefore if the first is greater than zero, lower than first will be incorrectly considered in the calculation. If the first is less than zero, negative values will be incorrectly disregarded. Therefore, to calculate the size of the slice I have to take into account only the elements between the first and the last. And so this:
    int quantidadeFatiasPorThread =  (last / threadNum);

Becomes that:

    int quantidadeFatiasPorThread = (last - first + 1) / threadNum;

Maybe you’re wondering why the + 1. His reason is that I want to also count the element of first. For example, if I want all elements 1 through 6, then I will want these elements: [1, 2, 3, 4, 5, 6], but if I do 6 - 1 the result is 5. Similarly if I want the elements [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], I have 11 elements, not 10 (can count to check if you doubt). This is basically because I am including, not excluding, the elements first. The purpose of + 1 also becomes obvious when the last and the first have the same value.

  1. Still in calculating the slice size we still have one more problem: if the division is not accurate, the rounding will be down. This causes at the end of the interval between first and last, some numbers may be left over without getting into any slice. To solve this you could use float or double, but it’s much easier to just round up if necessary:
    int quantidadeFatiasPorThread = (last - first + 1) / threadNum;
    if ((last - first + 1) % threadNum != 0) quantidadeFatiasPorThread++;
  1. Another interesting thing is this passage:
        if(ultimo > last){

            int quantidadeMais = ultimo - last;
            ultimo = ultimo - quantidadeMais;


        }

Let’s do a little math:

quantityMore = last - last
ultimo2 = last - quantityMore
Substituting quantityMore in the second equation we have:
last 2 = last - (last - last)
last 2 = last - last
ultimo2 = last

And this should be something obvious, because the ultimo cannot be greater than the last, the highest value he can assume is the last. You don’t need a more complicated formula for this. Soon:

        if (ultimo > last) ultimo = last;

Now your code should be working properly, but let’s improve it a little more.

  1. First these debug instructions:
        System.out.println(i+" "+threadNum);
         System.out.println("primeiro :"+primeiro+" ultimo: "+ultimo);

They look much better if you visualize it like this:

        System.out.println("i: " + i + " threadNum: " + threadNum + " primeiro: " + primeiro + " ultimo: " + ultimo);
  1. Let’s put an extra parameter in the method calcularQuantidadeDivisivel to specify the number of threads instead of leaving it fixed as 2. So this:
  public static int calcularQuantidadeDivisivel(final int first, final int last, final int divisor)
        throws InterruptedException, ExecutionException {

    int amount = 0;
    int threadNum = 2;

Becomes that:

public static int calcularQuantidadeDivisivel(int first, int last, int divisor)
        throws InterruptedException, ExecutionException
{
    return calcularQuantidadeDivisivel(first, last, divisor, 2);
}

public static int calcularQuantidadeDivisivel(int first, int last, int divisor, int threadNum)
        throws InterruptedException, ExecutionException
{

    int amount = 0;
  1. Finally, now is to test your code:
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("Caso 1: " + calcularQuantidadeDivisivel(0, 100, 9) + "\n");
        System.out.println("Caso 2: " + calcularQuantidadeDivisivel(1, 100, 9) + "\n");
        System.out.println("Caso 3: " + calcularQuantidadeDivisivel(2, 6, 3) + "\n");
        System.out.println("Caso 4: " + calcularQuantidadeDivisivel(5, 5, 3, 1) + "\n");
        System.out.println("Caso 5: " + calcularQuantidadeDivisivel(5, 7, 3, 3) + "\n");
        System.out.println("Caso 6: " + calcularQuantidadeDivisivel(5, 7, 3, 4) + "\n");
        System.out.println("Caso 7: " + calcularQuantidadeDivisivel(8, 8, 8, 4) + "\n");
    }

And see the exit:

Quantidade: 51
i: 0 threadNum: 2 primeiro: 0 ultimo: 50
i: 1 threadNum: 2 primeiro: 51 ultimo: 100
Caso 1: 12

Quantidade: 50
i: 0 threadNum: 2 primeiro: 1 ultimo: 50
i: 1 threadNum: 2 primeiro: 51 ultimo: 100
Caso 2: 11

Quantidade: 3
i: 0 threadNum: 2 primeiro: 2 ultimo: 4
i: 1 threadNum: 2 primeiro: 5 ultimo: 6
Caso 3: 2

Quantidade: 1
i: 0 threadNum: 1 primeiro: 5 ultimo: 5
Caso 4: 0

Quantidade: 1
i: 0 threadNum: 3 primeiro: 5 ultimo: 5
i: 1 threadNum: 3 primeiro: 6 ultimo: 6
i: 2 threadNum: 3 primeiro: 7 ultimo: 7
Caso 5: 1

Quantidade: 1
i: 0 threadNum: 4 primeiro: 5 ultimo: 5
i: 1 threadNum: 4 primeiro: 6 ultimo: 6
i: 2 threadNum: 4 primeiro: 7 ultimo: 7
i: 3 threadNum: 4 primeiro: 8 ultimo: 7
Caso 6: 1

Quantidade: 1
i: 0 threadNum: 4 primeiro: 8 ultimo: 8
i: 1 threadNum: 4 primeiro: 9 ultimo: 8
i: 2 threadNum: 4 primeiro: 9 ultimo: 8
i: 3 threadNum: 4 primeiro: 9 ultimo: 8
Caso 7: 1

And here’s the full code:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class FutureTaskDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        System.out.println("Caso 1: " + calcularQuantidadeDivisivel(0, 100, 9) + "\n");
        System.out.println("Caso 2: " + calcularQuantidadeDivisivel(1, 100, 9) + "\n");
        System.out.println("Caso 3: " + calcularQuantidadeDivisivel(2, 6, 3) + "\n");
        System.out.println("Caso 4: " + calcularQuantidadeDivisivel(5, 5, 3, 1) + "\n");
        System.out.println("Caso 5: " + calcularQuantidadeDivisivel(5, 7, 3, 3) + "\n");
        System.out.println("Caso 6: " + calcularQuantidadeDivisivel(5, 7, 3, 4) + "\n");
        System.out.println("Caso 7: " + calcularQuantidadeDivisivel(8, 8, 8, 4) + "\n");
    }

    public static int calcularQuantidadeDivisivel(int first, int last, int divisor)
            throws InterruptedException, ExecutionException
    {
        return calcularQuantidadeDivisivel(first, last, divisor, 2);
    }

    public static int calcularQuantidadeDivisivel(int first, int last, int divisor, int threadNum)
            throws InterruptedException, ExecutionException
    {
        int amount = 0;
        ExecutorService executor = Executors.newFixedThreadPool(threadNum);
        List<FutureTask<Integer>> taskList = new ArrayList<>(threadNum);

        int quantidadeFatiasPorThread = (last - first + 1) / threadNum;
        if ((last - first + 1) % threadNum != 0) quantidadeFatiasPorThread++;

        System.out.println("Quantidade: " + quantidadeFatiasPorThread);
        int primeiro = first;
        int ultimo = primeiro + quantidadeFatiasPorThread - 1;

        @SuppressWarnings("unchecked")
        FutureTask<Integer>[] arrayFutureTasks = (FutureTask<Integer>[]) new FutureTask<?>[threadNum];

        for (int i = 0; i < threadNum; i++) {
            System.out.println("i: " + i + " threadNum: " + threadNum + " primeiro: " + primeiro + " ultimo: " + ultimo);
            int primeiroFinal = primeiro;
            int ultimoFinal = ultimo;
            arrayFutureTasks[i] = new FutureTask<>(() -> calcularQuantidadeDivisivelSerial(primeiroFinal, ultimoFinal, divisor));

            taskList.add(arrayFutureTasks[i]);
            executor.execute(arrayFutureTasks[i]);

            primeiro = ultimo + 1;
            ultimo += quantidadeFatiasPorThread;
            if (ultimo > last) ultimo = last;
        }

        for (int j = 0; j < threadNum; j++) {
            FutureTask<Integer> futureTask = taskList.get(j);
            amount += futureTask.get();
        }

        executor.shutdown();

        return amount;
    }

    public static int calcularQuantidadeDivisivelSerial(int first, int last, int divisor) {
        int result = 0;
        for (int i = first; i <= last; i++) {
            if (i % divisor == 0) result++;
        }
        return result;
    }
}

There are still a few more improvements that I can suggest beyond that, but that remains for you to implement:

  1. Note that in cases where there are more threads than numbers in the interval between first and last, excess threads end up having the value of primeiro as being ultimo + 1. This is not harmful as it will cause the method calcularQuantidadeDivisivelSerial return zero, but end up creating unnecessary threads. If you want to fix this, just restrict the value of threadNum to never be greater than last - first + 1.

  2. Another improvement you should make is to put the executor.shutdown(); within a block finally with the rest of the method inside the block try. Otherwise, if for some reason an exception occurs, your executor will not become a kind of memory-Leak.

  3. Validate the parameters properly: Do not leave the divisor be zero, make sure that first <= last and make sure that threadNum > 0.

  • Thank you so much for sharing your knowledge!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.