Het Runnable-probleem

U bent al bekend met de Runnable- interface en de Thread- klasse die deze implementeert. Laten we ons herinneren hoe deze interface eruit ziet:


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

Merk op dat het retourtype van de methode run is void . Maar wat als we onze thread nodig hebben om een ​​resultaat van zijn werk terug te geven in de vorm van een getal, een string of een ander object? Dan moeten we een oplossing bedenken. Iets zoals dit:


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

Laten we de volgende hoofdmethode uitvoeren :


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

De console zal het volgende weergeven:

Fibonacci-getal 10: 34

Deze code heeft verschillende nadelen. Als gevolg van de aanroep van de join- methode zal de hoofdthread bijvoorbeeld blokkeren terwijl de printByIndex- methode wordt uitgevoerd.

Oproepbare interface

Laten we nu eens kijken naar de interface die Java ons out-of-the-box biedt, die kan worden gebruikt als alternatief voor Runnable . Dit is de oproepbare interface:


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

Zoals je kunt zien, heeft het, net als Runnable , maar één methode. Deze methode heeft hetzelfde doel als de run- methode: het bevat de code die in een parallelle thread wordt uitgevoerd. Wat verschillen betreft, kijk eens naar de retourwaarde. Nu kan het elk type zijn dat u opgeeft bij het implementeren van de interface:


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

Een ander voorbeeld:


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

Hier is nog iets nuttigs: de call- methode kan een Exception genereren . Dat betekent dat we, in tegenstelling tot de run- methode, in de call -methode niet de gecontroleerde uitzonderingen hoeven af ​​te handelen die binnen de methode voorkomen:


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;

	}

}

Toekomstige interface

Een andere interface die nauw samenwerkt met Callable is Future . Future vertegenwoordigt het resultaat van asynchrone (parallelle) berekeningen (de waarde die wordt geretourneerd door de call- methode). Hiermee kunt u controleren of de berekeningen zijn voltooid, wachten tot de berekeningen zijn voltooid, het resultaat van de berekeningen ophalen en meer.

Methoden van de toekomstige interface

  • boolean isDone() — deze methode retourneert true als deze taak (berekening) is voltooid. Taken die normaal zijn geëindigd, met een uitzondering zijn geëindigd of zijn geannuleerd, worden als voltooid beschouwd.

  • V get() — indien nodig blokkeert deze methode de thread die het heeft aangeroepen en geeft het resultaat van de berekeningen terug wanneer ze klaar zijn.

  • V get (lange time-out, TimeUnit-eenheid) - net als de vorige methode blokkeert deze methode de thread die deze heeft aangeroepen, wachtend op het resultaat, maar alleen gedurende de tijd die is opgegeven door de methodeparameters.

  • boolean cancel(boolean mayInterruptIfRunning) — deze methode probeert de uitvoering van de taak te stoppen. Als de taak nog niet is gestart, zal deze nooit worden uitgevoerd. Als de taak bezig was, bepaalt de parameter mayInterruptIfRunning of er een poging zal worden gedaan om de thread die de taak uitvoert te onderbreken. Nadat de methode annuleren is aangeroepen, retourneert de methode isDone altijd true .

  • boolean isCancelled() — deze methode retourneert true als de taak wordt geannuleerd voordat deze normaal is voltooid. De methode retourneert altijd true als de annuleermethode eerder is aangeroepen en true heeft geretourneerd .

Voorbeeld van code met Callable en 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;
 
	}
 
}

Laten we de volgende hoofdmethode uitvoeren :


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

De console zal het volgende weergeven:

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