Lab - Wątki

Runnable

Runnable to interfejs funkcyjny w Javie, który definiuje jedną metodę run(). Reprezentuje on zadanie do wykonania w osobnym wątku, jednak w przeciwieństwie do Callable nie zwraca żadnej wartości ani nie zgłasza sprawdzanych wyjątków. Aby uruchomić kod zapisany w klasie implementującej Runnable, przekazujemy jej obiekt do konstruktora klasy Thread lub wykorzystujemy go w ramach ExecutorService, co pozwala wygodnie zarządzać wieloma zadaniami wykonywanymi współbieżnie.

Przykład:

public class MyRunnable implements Runnable { private String taskName;
    public MyRunnable(String taskName) {
        this.taskName = taskName;
    }
    
    @Override
    public void run() {
        System.out.println("Zadanie " + taskName + " wykonuje wątek: " 
                           + Thread.currentThread().getName());
    }
}

Przykład uruchomienia

public class RunnableExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new MyRunnable("A"));
        Thread thread2 = new Thread(new MyRunnable("B"));

        thread1.start();
        thread2.start();
    }
}

Krótsza forma

public class RunnableExample {
    public static void main(String[] args) {
        Runnable task = () -> {
            System.out.println("Wątek działa: " + Thread.currentThread().getName());
        };

        Thread thread = new Thread(task);
        thread.start(); // uruchamia nowy wątek
    }
}

Callable

Callable<V> to interfejs funkcyjny w Javie, który posiada jedną metodę call(). Służy on do reprezentowania zadań wykonywanych w osobnych wątkach, przy czym metoda call() zwraca wynik określonego typu V oraz może zgłaszać wyjątki (Exception). Aby odebrać wartość obliczoną w wątku, korzystamy z obiektów Future, natomiast do zarządzania większą liczbą wątków stosujemy mechanizmy oferowane przez ExecutorService.

Przykład

import java.util.concurrent.Callable;

// Klasa implementująca interfejs Callable
public class MyCallable implements Callable<Integer> {
    private int number;

    public MyCallable(int number) {
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        System.out.println("Obliczam kwadrat liczby " + number 
                           + " w wątku: " + Thread.currentThread().getName());
        return number * number;
    }
}

Przykład uruchomienia

import java.util.concurrent.*;

public class CallableExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Future<Integer> result = executor.submit(new MyCallable(5));

        System.out.println("Wynik obliczeń: " + result.get());

        executor.shutdown();
    }
}

Krótsza forma

import java.util.concurrent.*;

public class CallableExample {
    public static void main(String[] args) throws Exception {
        Callable<Integer> task = () -> {
            System.out.println("Obliczam wynik w wątku: " + Thread.currentThread().getName());
            return 2 + 3;
        };

        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(task);

        System.out.println("Wynik obliczeń: " + future.get()); // blokuje do zakończenia obliczeń

        executor.shutdown();
    }
}

Pobieranie wartości z watka wykorzystującego interfejsu Calleble odbywa się z wykorzystaniem obiektów Future<V> . Tworząc obiekt Future musimy podać ten sam typ co zwraca wątek np.

Future<Integer> future = executor.submit(task);

w powyższym przykładzie tworzymy obiekt, który ma przechować wartość Integer, jednocześnie przypisujemy mu zadanie, które ma się wykonać w wątku. Następnym krokiem jest pobranie wyniku z wątku.

Podsumowanie

Cecha

Runnable

Callable<V>

Metoda

void run()

V call() throws Exception

Zwracany wynik

brak (zawsze void)

zwraca wynik typu V

Obsługa wyjątków

tylko wyjątki niekontrolowane (RuntimeException)

może rzucać wyjątki sprawdzane (Exception)

Użycie

proste zadania bez potrzeby zwracania danych

gdy chcemy zwrócić wynik obliczeń lub obsłużyć wyjątek

Najczęstszy sposób uruchomienia

new Thread(runnable).start()

ExecutorService.submit(callable)Future

Dodano w Javie

Java 1.0

Java 5 (wraz z pakietem java.util.concurrent)

ExecutorService

Obiekty ExecutorService pozwalają na zarządzać pulą wątków (thread pool) i uruchamiać zadania asynchronicznie, bez konieczności ręcznego tworzenia i kontrolowania wątków ( ponowne używanie, zamykanie).

Zazwyczaj tworzy się je przez fabryczne metody klasy Executors

Tworzenie wątków

Wykorzystując obiekt ExecutorService mamy możliwość stworzenia różnych wątków w zależności od potrzeb

Metoda

Opis

Executors.newSingleThreadExecutor()

Jeden wątek wykonujący zadania sekwencyjnie.

Executors.newFixedThreadPool(int n)

Stała liczba wątków – dobre przy przewidywalnym obciążeniu.

Executors.newCachedThreadPool()

Dynamiczna pula wątków – dopasowuje się do liczby zadań.

Executors.newScheduledThreadPool(int n)

Pozwala uruchamiać zadania cyklicznie lub z opóźnieniem.

Dodawanie zadań do wątków

Wykorzystując obiekt ExecutroService możemy dodweaać większą liczbę zadań do naszych wątków

Zamykanie wątków

Ważnym etapem pracy z wątkami jest zamykanie ich, można wykonać to na kilka sposobów:

Podstawowe wywołanie zamknięcia wątków

executor.shutdown();

Natychmiastowa wywołanie zamknięcia wątków, może powodować wyjątek

executor.shutdownNow();

Lub określenie czasu po jakim mają zakończyć prace.

executor.awaitTermination(10, TimeUnit.SECONDS);

Zadania

  1. Stwórz wątek który, będzie co trzy sekundy skanował wybrany folder w poszukiwaniu plików. Wątek powinien informować o zmianach w podanej lokalizacji

  2. Stwórz wątek, który będzie co stały czas skanował folder i sprawdzał czy pojawiły się w nim pliki. Następnie przenieś pliki po rozszerzeniach np. pliki txt, doc, csv, pdf do folderu dokumenty, pliki: png, jpg, bmp do folderu image, pozostałe pliki do folderu różne.

  3. Wykorzystując dowolny rodzaj wątku wyszukaj liczby pierwsze dla zakresu 1 000 000. Do zadania wykorzystaj 4 wątki.

  4. Napisz program, który wykorzystując wielowątkowość będzie losował 1000 liczb spełniających warunek podzielności przez 3. Wszystkie liczby spełniające ten warunek dodawaj do wspólnej listy, do rozwiązania wykorzystaj dowolny rodzaj wątków.

  5. Sprawdź czy jest różnica czasowa w wykonaniu zadania 3 i 4 przy wykorzystaniu różnej liczby wątków np. 1, 4 i 6.

Last updated