Il problema eseguibile

Hai già familiarità con l' interfaccia Runnable e la classe Thread che la implementa. Ricordiamo come appare questa interfaccia:


public interface Runnable {
	public abstract void run();
}

Si noti che il tipo restituito del metodo run è void . Ma cosa succede se abbiamo bisogno che il nostro thread restituisca un risultato del suo lavoro sotto forma di un numero, una stringa o qualche altro oggetto? Quindi dobbiamo trovare una soluzione alternativa. Qualcosa come questo:


public class Fibonacci implements Runnable {
 
 
 
	private final int index;
 
	private int result;
 
 
 
	public Fibonacci(int index) {
 
    		this.index = index;
 
	}
 
 
 
	@Override
 
	public void run() {
 
    		int first = 0;
 
    		int second = 1;
 
    		if (index == 1) {
 
        			result = first;
 
    		} else if (index == 2) {
 
        			result = second;
 
    		} else {
 
        			for (int i = 0; i < index - 2; i++) {
 
            				int temp = second;
 
            				second += first;
 
            				first = temp;
 
        			}
 
 
 
            			result = second;
 
    		}
 
	}
 
 
 
	public static void printByIndex(int index) throws InterruptedException {
 
    		Fibonacci fibonacci = new Fibonacci(index);
 
    		Thread thread = new Thread(fibonacci);
 
    		thread.start();
 
    		thread.join();
 
    		System.out.println("Fibonacci number " + index + ": " + fibonacci.result);
 
	}
 
}

Eseguiamo il seguente metodo principale :


	public static void main(String[] args) throws Exception {
    		Fibonacci.printByIndex(10);
	}

La console visualizzerà:

Fibonacci numero 10: 34

Questo codice ha diversi inconvenienti. Ad esempio, come risultato della chiamata al metodo join , il thread principale si bloccherà mentre viene eseguito il metodo printByIndex .

Interfaccia richiamabile

Ora diamo un'occhiata all'interfaccia che Java ci fornisce fuori dagli schemi, che può essere utilizzata come alternativa a Runnable . Questa è l' interfaccia Callable :


public interface Callable<V> {
 
	V call() throws Exception;
 
}

Come puoi vedere, proprio come Runnable , ha un solo metodo. Questo metodo ha lo stesso scopo del metodo run : contiene il codice che verrà eseguito in un thread parallelo. Per quanto riguarda le differenze, dai un'occhiata al valore di ritorno. Ora può essere qualsiasi tipo specificato durante l'implementazione dell'interfaccia:


public class CurrentDate implements Callable<Long> {
 
	@Override
 
	public Long call() {
 
    		return new Date().getTime();
 
	}
 
}

Un altro esempio:


Callable<String> task = () -> {
 
	Thread.sleep(100);
 
	return "Done";
 
};

Ecco qualcos'altro di utile: il metodo call può generare un'eccezione . Ciò significa che, a differenza del metodo run , nel metodo call non dobbiamo gestire le eccezioni verificate che si verificano all'interno del metodo:


public class Sleep implements Runnable {

	@Override

	public void run() {

    	    try {

        	        Thread.sleep(1000);

    	    } catch (InterruptedException ignored) {

    	    }

	}

}

public class Sleep implements Callable {

	@Override

	public Object call() throws InterruptedException {

    	    Thread.sleep(1000);

    	    return null;

	}

}

Interfaccia futura

Un'altra interfaccia che funziona a stretto contatto con Callable è Future . Future rappresenta il risultato di calcoli asincroni (paralleli) (il valore restituito dal metodo di chiamata ). Ti consente di verificare se i calcoli sono stati eseguiti, attendere il completamento dei calcoli, ottenere il risultato dei calcoli e altro ancora.

Metodi dell'interfaccia del futuro

  • boolean isDone() — questo metodo restituisce true se questa attività (calcolo) è stata eseguita. Le attività che sono terminate normalmente, sono terminate con un'eccezione o sono state annullate sono considerate completate.

  • V get() — se necessario, questo metodo blocca il thread che lo ha chiamato e restituisce il risultato dei calcoli al termine.

  • V get(long timeout, TimeUnit unit) — come il metodo precedente, questo metodo blocca il thread che lo ha chiamato, attendendo il risultato, ma solo per il tempo specificato dai parametri del metodo.

  • boolean cancel(boolean mayInterruptIfRunning) : questo metodo tenta di interrompere l'esecuzione dell'attività. Se l'attività non è ancora stata avviata, non verrà mai eseguita. Se l'attività era in corso, il parametro mayInterruptIfRunning determina se verrà effettuato un tentativo di interrompere il thread che esegue l'attività. Dopo che il metodo cancel è stato chiamato, il metodo isDone restituirà sempre true .

  • boolean isCancelled() — questo metodo restituisce true se l'attività viene annullata prima che termini normalmente. Il metodo restituirà sempre true se il metodo cancel è stato precedentemente chiamato e ha restituito true .

Esempio di codice che utilizza Callable e Future


import java.util.HashMap;
 
import java.util.Map;
 
import java.util.concurrent.*;
 
 
 
public class Fibonacci implements Callable<Integer> {
 
 
 
	private final int index;
 
 
 
	public Fibonacci(int index) {
 
    		this.index = index;
 
	}
 
 
 
	@Override
 
	public Integer call() {
 
    		int first = 0;
 
    		int second = 1;
 
    		if (index == 1) {
 
        			return first;
 
    		} else if (index == 2) {
 
        			return second;
 
    		} else {
 
        		for (int i = 0; i < index - 2; i++) {
 
            			int temp = second;
 
            			second += first;
 
            			first = temp;
 
        		}
 
 
 
        			return second;
 
    		}
 
	}
 
 
 
	public static Future<Integer> calculateAsync(int index) throws Exception {
 
    		Fibonacci fibonacci = new Fibonacci(index);
 
 
 
    		// The future object will represent the result of running the fibonacci task.
 
    		FutureTask<Integer> future = new FutureTask<>(fibonacci);
 
 
 
    		// Because the FutureTask class implements both the Future interface and the Runnable interface,
 
	 	// you can pass instances of it to the Thread constructor
 
    		Thread thread = new Thread(future);
 
    		thread.start();
 
 
 
    		return future;
 
	}
 
}

Eseguiamo il seguente metodo principale :


	public static void main(String[] args) throws Exception {
    		Map<Integer, Future<Integer>> tasks = new HashMap<>();
    		for (int i = 10; i < 20; i++) {
        			tasks.put(i, Fibonacci.calculateAsync(i));
    		}
 
    		for (Map.Entry<Integer, Future<Integer>> entry : tasks.entrySet()) {
        			Future<Integer> task = entry.getValue();
        			int index = entry.getKey();
        			int result;
        			// Check whether the task is done
        			if (task.isDone()) {
            				// Get the result of the calculations
            				result = task.get();
        			} else {
            				try {
                				// Wait another 100 milliseconds for the result of the calculations
                				result = task.get(100, TimeUnit.MILLISECONDS);
            				} catch (TimeoutException e) {
                				// Interrupt the task
                				task.cancel(true);
                				System.out.println("Fibonacci number " + index + " could not be calculated in the allotted time.");
                				return;
            				}
        			}
        			System.out.println("Fibonacci number " + index + ": " + result);
    		}
	}

La console visualizzerà:

Fibonacci numero 16: 610
Fibonacci numero 17: 987
Fibonacci numero 18: 1597
Fibonacci numero 19: 2584
Fibonacci numero 10: 34 Fibonacci numero
11: 55 Fibonacci numero
12: 89
Fibonacci numero 13: 144
Fibonacci numero 14: 233
Fibonacci numero 15: 377