Das Runnable-Problem

Sie sind bereits mit der Runnable- Schnittstelle und der Thread- Klasse vertraut, die sie implementiert. Erinnern wir uns, wie diese Schnittstelle aussieht:


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

Beachten Sie, dass der Rückgabetyp der Ausführungsmethode void ist . Was aber, wenn unser Thread ein Ergebnis seiner Arbeit in Form einer Zahl, eines Strings oder eines anderen Objekts zurückgeben soll? Dann müssen wir einen Workaround finden. Etwas wie das:


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);
 
	}
 
}

Lassen Sie uns die folgende Hauptmethode ausführen :


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

Die Konsole zeigt Folgendes an:

Fibonacci-Zahl 10: 34

Dieser Code hat mehrere Nachteile. Beispielsweise wird durch den Aufruf der Join- Methode der Hauptthread blockiert, während die printByIndex- Methode ausgeführt wird.

Aufrufbare Schnittstelle

Schauen wir uns nun die Schnittstelle an, die uns Java standardmäßig zur Verfügung stellt und die als Alternative zu Runnable verwendet werden kann . Dies ist die Callable- Schnittstelle:


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

Wie Sie sehen, gibt es genau wie Runnable nur eine Methode. Diese Methode dient demselben Zweck wie die run- Methode – sie enthält den Code, der in einem parallelen Thread ausgeführt wird. Was Unterschiede betrifft, werfen Sie einen Blick auf den Rückgabewert. Jetzt kann es sich um einen beliebigen Typ handeln, den Sie bei der Implementierung der Schnittstelle angeben:


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

Ein anderes Beispiel:


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

Hier ist noch etwas Nützliches: Die Methode call kann eine Ausnahme auslösen . Das bedeutet, dass wir im Gegensatz zur run- Methode in der call- Methode nicht die geprüften Ausnahmen behandeln müssen, die innerhalb der Methode auftreten:


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;

	}

}

Zukünftige Schnittstelle

Eine weitere Schnittstelle, die eng mit Callable zusammenarbeitet, ist Future . Future stellt das Ergebnis asynchroner (paralleler) Berechnungen dar (den von der Aufrufmethode zurückgegebenen Wert ). Sie können damit überprüfen, ob die Berechnungen abgeschlossen sind, auf den Abschluss der Berechnungen warten, das Ergebnis der Berechnungen abrufen und vieles mehr.

Methoden der Zukunftsschnittstelle

  • boolean isDone() – diese Methode gibt true zurück , wenn diese Aufgabe (Berechnung) abgeschlossen ist. Aufgaben, die normal beendet wurden, mit einer Ausnahme endeten oder abgebrochen wurden, gelten als erledigt.

  • V get() – Bei Bedarf blockiert diese Methode den Thread, der sie aufgerufen hat, und gibt das Ergebnis der Berechnungen zurück, wenn diese abgeschlossen sind.

  • V get(long timeout, TimeUnit unit) – wie die vorherige Methode blockiert diese Methode den Thread, der sie aufgerufen hat, und wartet auf das Ergebnis, jedoch nur für die durch die Methodenparameter angegebene Zeit.

  • boolean cancel(boolean mayInterruptIfRunning) – diese Methode versucht, die Ausführung der Aufgabe zu stoppen. Wenn die Ausführung der Aufgabe noch nicht begonnen hat, wird sie nie ausgeführt. Wenn die Aufgabe ausgeführt wurde, bestimmt der Parameter mayInterruptIfRunning , ob versucht wird, den Thread zu unterbrechen, der die Aufgabe ausführt. Nachdem die Methode cancel aufgerufen wurde, gibt die Methode isDone immer true zurück .

  • boolean isCancelled() – diese Methode gibt „true“ zurück , wenn die Aufgabe abgebrochen wird, bevor sie normal abgeschlossen wird. Die Methode gibt immer true zurück , wenn die Methode cancel zuvor aufgerufen wurde und true zurückgegeben hat .

Beispielcode mit Callable und 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;
 
	}
 
}

Lassen Sie uns die folgende Hauptmethode ausführen :


	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);
    		}
	}

Die Konsole zeigt Folgendes an:

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