Obraz zawierający tekst, Czcionka, Grafika

Opis wygenerowany automatycznie 

Kierunek Informatyka

 

Instrukcja do ćwiczeń laboratoryjnych nr:

7

Nazwa przedmiotu:
Programowanie aplikacji mobilnych

Temat:  Współbieżne wykonywanie zadań – wątki

Tryb studiów: stacjonarne

Czas trwanie ćw.

2x45 min

Autor materiałów: dr Marcin Skuba

 

1. Treści programowe:

Równoległe wykonywanie operacji. Wątki. Kolejki komunikatów.

 

2. Cel zajęć:

Celem zajęć jest zrozumienie działania współpracy współbieżnie wykonywanych zadań w ramach kilku wątków programu oraz zapoznanie się ze strukturą programu, gdzie ważnym elementem jest przekazywanie komunikatów pomiędzy wątkami.

 

3. Materiały dydaktyczne

 

ü Thread

 

W Androidzie główny wątek UI to wątek, na którym działa interfejs użytkownika.

·        Jest tworzony automatycznie przy starcie aplikacji.

·        Obsługuje wszystkie operacje na widokach (TextView, Button, RecyclerView itp.).

·        Nie wolno w nim wykonywać długich operacji, bo spowoduje to zamrożenie UI (ANR).

·        Możesz w nim używać Handler(Looper.getMainLooper()), runOnUiThread() lub Kotlin lifecycleScope.launch(Dispatchers.Main) do aktualizacji widoków.


Nie wolno modyfikować UI (TextView, Button itd.) spoza wątku głównego.

 

Runnable

Interfejs, który reprezentuje zadanie do wykonania.
Zawiera jedną metodę: void run(); Jest to kod, który można uruchomić w wątku lub przez Handler.

 

Thread

Fizyczny wątek wykonania w systemie.
Umożliwia wykonywanie kodu równolegle z innymi wątkami.

Można również:

·        uruchomić Runnable w Thread,

·        tworzyć nowe wątki,

·        współpracować z Handlerem.

 

Handler

Obiekt służący do przesyłania zadań (Runnable/Message) do wątku powiązanego z określonym Looperem.
Handler odpowiada za:

·        post(Runnable) - wykonanie kodu w innym wątku

·        sendMessage() - wysyłanie wiadomości do obsługi

·        wywoływanie handleMessage() lub Runnable

 

Looper

Pętla zdarzeń działająca w wątku.
Jej zadaniem jest:

·        ciągle pobierać zadania z MessageQueue

·        przekazywać je do Handlera

·        działać dopóki wątek żyje

Główny wątek Androida ma Looper domyślnie.

 

Looper.getMainLooper() - Tworzy Handler powiązany z głównym wątkiem UI .Zawsze działa niezależnie od tego, w którym wątku jesteś.

 

Looper.myLooper() -  zwraca Looper bieżącego wątkuCzyli:

·        jeśli kod działa w głównym wątku - zwróci Looper głównego wątku

·        jeśli kod działa w zwykłym Thread - zwróci null (bo nie ma Looper’a)

 

Handler używa Looper’a, aby wiedzieć, na którym wątku ma odbierać wiadomości i aktualizować UI.

 

MessageQueue

Kolejka zadań (ang. MessageQueue) powiązana jest z Looperem.
Przechowuje:

·        Runnable (gdy używasz post())

·        Message (gdy używasz sendMessage())

Looper pobiera z niej zadania w pętli i przekazuje do Handlera.

 

Wszystkie wywołania post(), postDelayed(), sendMessage() trafiają do kolejki

 

Callback (Handler.Callback)

Interfejs, który pozwala przechwycić Message zanim Handler wywoła handleMessage().

Jeśli Callback zwróci true, wiadomość jest uznana za obsłużoną i nie trafi do handleMessage()

 

Prosty opis:

·        Looper = „pracownik”, który cały czas odbiera i obsługuje wiadomości

·        MessageQueue = „stos dokumentów”, które czekają na realizację

·        Handler = „sekretarka”, która wysyła dokumenty do pracownika

·        Looper.myLooper() = „daj mi pracownika tego biura (tego wątku)”

 

------------------------------------------------------------------------------------------------

Przekazywanie danych z „naszego” (drugiego  wątku) do wątku głównego UI.

 

·       runOnUiThread(()

 

Kiedy używamy runOnUiThread()?

·       gdy jesteś w Activity

·       gdy chcesz natychmiast wykonać kod na UI Thread

·       gdy nie masz swojego Handlera

 

Wady:

 

Przykład 1

TextView textView = findViewById(R.id.textView);


new Thread(new Runnable() {
   
@Override
   
public void run() {

        String mmessage =
"Dane z wątku drugiego";
        runOnUiThread(() ->
textView.setText("Wynik: " + mmessage));
    }
}).start();

 

Lub

TextView textView = findViewById(R.id.textView);
new Thread(new Runnable() {
   
@Override
   
public void run() {
        String mmessage =
"Dane z wątku drugiego";
        runOnUiThread(
new Runnable() {
           
@Override
           
public void run() {
               
textView.setText("Wynik: " + mmessage);
            }
        });
    }
}).start();

 

 

·       handleMessage

 

Każdy Handler jest powiązany z pewnym Looperem.

Najczęściej:

·        new Handler(Looper.getMainLooper()) - kolejka głównego wątku UI

·        new Handler(HandlerThread.getLooper()) - kolejka wątku roboczego

 

Handler otrzymuje wiadomość i wywołuje handleMessage()

Jeśli w kolejce znajdzie się Message, Handler przetwarza je:

·        Jeżeli Handler ma ustawiony Callback  wywoła się callback.handleMessage(msg)

·        Jeśli nie wywoła się metoda handleMessage(msg) Twojego Handlera

Czyli to jest miejsce, w którym odbierasz i przetwarzasz zadanie wysłane do Handlera.

 

 

Przykład 2

 

    TextView textView = findViewById(R.id.textView);
    Handler mainHandler =
new Handler(Looper.getMainLooper()){
       
@Override
       
public void handleMessage(Message msg) {
           
super.handleMessage(msg);
           
textView.setText("Wynik: " + msg.obj);
        }
    };

   
new Thread(new Runnable() {
       
@Override
       
public void run() {
            Message msg =
new Message();
            msg.
obj = "Dane z wątku drugiego";
           
//mainHandler.sendMessage(msg);
           
runOnUiThread(new Runnable() {
               
@Override
               
public void run() {
                   
textView.setText("Wynik: " + msg.obj);
                }
            });
        }
    }).start();
}

 

 

·       mainHandler.post()

 

Kiedy używamy handler.post()?

·       Gdy masz Handler powiązany z Looper.getMainLooper() (UI Thread)

·       Gdy wykonujesz kod z dowolnego miejsca aplikacji

·       Gdy masz własny mechanizm komunikacji między wątkami

·       Gdy chcesz odroczyć wykonanie lub wysyłać wiadomości seryjnie

 

Główne cechy:

·       działa wszędzie: wątki, klasy, serwisy, worker threads

·       Handler może obsługiwać kolejkę zdarzeń

·       można użyć postDelayed()

·       jest podstawą pracy wątków w Androidziess

 

handler.post(Runnable r) - umieszcza Runnable na końcu kolejki Looper i wykonuje go jak najszybciej, gdy kolejka do niego dotrze.

handler.postDelayed(Runnable r, long delayMillis) - umieszcza Runnable w kolejce, ale z opóźnieniem delayMillis milisekund (wykona się po upływie tego czasu, gdy Looper przetworzy wiadomość).

 

 

Przykład 3

TextView textView = findViewById(R.id.textView);
Handler mainHandler =
new Handler(Looper.getMainLooper());

new Thread(new Runnable() {
   
@Override
   
public void run() {
        String message =
"Dane z wątku drugiego";
       
mainHandler.post(() -> textView.setText("Wynik: " + message));
    }
}).start();

 

Lub

TextView textView = findViewById(R.id.textView);
Handler mainHandler =
new Handler(Looper.getMainLooper());

new Thread(new Runnable() {
   
@Override
   
public void run() {
        String message =
"Dane z wątku drugiego";
       
mainHandler.post(new Runnable() {
           
@Override
           
public void run() {
               
textView.setText("Wynik: " + message);
            }
        });
    }
}).start();

 

Przykład, w którym przesyłane jest dwie wartości z dwóch dodatkowych wątków (klasa MainActivity):

 

import androidx.appcompat.app.AppCompatActivity;
import
android.os.Bundle;
import
android.os.Handler;
import
android.os.Message;
import
android.widget.TextView;

public class
MainActivity extends AppCompatActivity {

    Handler
handler; // Obiekt handler służy do przekazywania wiadomości pomiędzy wątkami
   
TextView textView1;
    private
TextView textView2;

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
       
setContentView(R.layout.activity_main);

       
textView1 = (TextView) findViewById(R.id.textView1);
       
textView2 = (TextView) findViewById(R.id.textView2);

       
// W tym miejscu handler zdefiniowany jest jako odbiorca z metodą handleMessage wywoływaną wtedy, gdy nadchodzi wiadomość np. z innego wątku
       
handler = new Handler(Looper.myLooper()){
           
@Override
           
public void handleMessage(Message msg) {
               
super.handleMessage(msg);

               
Bundle bundle = msg.getData();
                int
licznik1 = bundle.getInt("LICZNIK_1");
                int
licznik2 = bundle.getInt("LICZNIK_2");

                if
(bundle.get("LICZNIK_1")!=null)
                   
textView1.setText("Licznik 1: "+licznik1);
                if
(bundle.get("LICZNIK_2")!=null)
                   
textView2.setText("Licznik 2: "+licznik2);
           
}
        }
;

       
// Instancja obiektu wątku stworzonego w oddzielnym pliku Watek2.java
       
new Watek2(handler);

       
// instancja obiektu wątku stworzonego w tej samej klasie, w której obsługiwany jest interfejs użytkownika
       
new Thread (new Runnable() {
           
int licznik=0;

           
@Override
           
public void run() {
               
while(true){
                   
try {
                       
licznik++;
                       
Message message = new Message();    // obiekt wiadomości
                        
message.obj=licznik;                // dodanie wartości do wiadomości
                       
Bundle bundle = new Bundle();       //"Tobołek", do którego wrzucane będą dane przesyłane handlerem. Bundle pozwala przesyłać dane w formie klucz – wartość, różnego typu.
                       
bundle.putInt("LICZNIK_1", licznik);// Wstawienie wartości licznika do obiektu bundle z kluczem "LICZNIK_1"
                       
message.setData(bundle);            // Wstawienie obiektu bundle do wiadomości
                       
handler.sendMessage(message);       // przesłanie wiadomości do Handlera

                       
Thread.sleep(1000);            // opóźnienie 1 sekunda
                   
} catch (InterruptedException e) {
                        e.printStackTrace()
;
                   
}
                }
            }
        }).start()
;
   
}
}

 

Klasa zawierający oddzielny wątek (Watek2.java):

 

import android.os.Bundle;
import
android.os.Handler;
import
android.os.Message;

public class
Watek2 implements Runnable {
    Handler
handler;
    int
licznik =0;
   
Thread thread;

   
Watek2(Handler handler){
       
this.handler=handler;
       
thread=new Thread(this);
       
thread.start();
   
}

   
@Override
   
public void run() {
       
while(true){
           
licznik++;
           
Message message = new Message();
           
message.obj=licznik;
           
Bundle bundle = new Bundle();
           
bundle.putInt("LICZNIK_2", licznik);
           
message.setData(bundle);
           
handler.sendMessage(message);

            try
{
                Thread.sleep(
500);
           
} catch (InterruptedException e) {
                e.printStackTrace()
;
           
}
        }
    }
}

 

plik activity_main.xml:

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
   
xmlns:app="http://schemas.android.com/apk/res-auto"
   
xmlns:tools="http://schemas.android.com/tools"
   
android:layout_width="match_parent"
   
android:layout_height="match_parent"
   
android:orientation="vertical"
   
android:gravity="center"
   
tools:context=".MainActivity">

    <TextView
   
android:id="@+id/textView1"
   
android:layout_width="wrap_content"
   
android:layout_height="wrap_content"
   
android:text="Czas 1"
   
android:textSize="32dp"
   
android:textColor="#00f"/>

    <TextView
       
android:id="@+id/textView2"
       
android:layout_width="wrap_content"
       
android:layout_height="wrap_content"
       
android:text="Czas 2"
       
android:textSize="32dp"
       
android:textColor="#f00"/>

</LinearLayout>

 

·       postInvalidate()

 

Do odświeżenia wyglądu widoku z innego wątku niż UI służy metoda postInvalidate().

Ustawia flagę, że widok wymaga odświeżenia (ponownego narysowania).

Wysyła to żądanie BEZPOŚREDNIO do głównego wątku, nawet jeśli wywołasz ją z wątku roboczego.

Powoduje wywołanie onDraw() w najbliższym cyklu renderowania UI.

 

---------------------------------------------------------------------------------------------------

ü Executors

 

Executors to klasa dostarczająca gotowe pule wątków, które:

 

-  Executors - zarządza wątkami za Ciebie.

-  ExecutorService - wykonuje kod w tle.

 

 

Najpopularniejsze typy Executorów:

 

1. Single Thread Executor

Jeden wątek, zadania wykonują się po kolei:

 

ExecutorService executor = Executors.newSingleThreadExecutor();

 

2. Fixed Thread Pool

Określona liczba wątków pracujących równolegle:

 

ExecutorService executor = Executors.newFixedThreadPool(4);

 

3. Cached Thread Pool

Tworzy nowe wątki dynamicznie, gdy ich potrzebuje:

 

ExecutorService executor = Executors.newCachedThreadPool();

 

-----------------------------------------------------------------------------------------------------------------

Przykład wykorzystania klasy ExecutorService oraz Executors.newSingleThreadExecutor():

 

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {
    ExecutorService
executor;
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);
        TextView textView = findViewById(R.id.
textView);
       
       
// Executor – praca w tle
        
executor = Executors.newSingleThreadExecutor();

       
// Handler – do aktualizacji UI
       
Handler handler = new Handler(Looper.getMainLooper());

       
executor.execute(new Runnable() {
           
@Override
           
public void run() {
               
// ---- [MÓJ WĄTEK] ----
                // Ciężka operacja (np. obliczenia, pobieranie danych)
               
String wynik = "Dane z wątku roboczego";

               
// Przekazanie wyniku na wątek główny
                
handler.post(new Runnable() {
                   
@Override
                   
public void run() {
                       
// ---- [WĄTEK UI] ----
                       
textView.setText(wynik);
                    }
                });
            }
        });
    }

   
@Override
   
protected void onDestroy() {
       
super.onDestroy();
       
// Niezbędne do poprawnego zamknięcia wątków
       
if (executor != null) {
           
executor.shutdown();
        }
}

 

Dobrze jest wyłączyć executor, kiedy Activity się niszczy executor.shutdown();

 

-----------------------------------------------------------------------------------------------------------------

Przykład wykorzystania klasy ExecutorService oraz Executors.newFixedThreadPool(3):

 

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {


    ExecutorService
executor;
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);
        TextView textView = findViewById(R.id.
textView);

       
executor = Executors.newFixedThreadPool(3);

// Handler do aktualizacji UI
       
Handler uiHandler = new Handler(Looper.getMainLooper());

// Zadanie 1
       
executor.execute(new Runnable() {
           
@Override
           
public void run() {
               
// ---- Wątek tła ----
               
int wynik1 = 10 + 20; // symulacja
               
SystemClock.sleep(1000);

               
uiHandler.post(() -> textView.append("Wynik 1: " + wynik1 + "\n"));
            }
        });

// Zadanie 2
       
executor.execute(new Runnable() {
           
@Override
           
public void run() {
               
// ---- Wątek tła ----
               
String wynik2 = "Dane pobrane z serwera";
                SystemClock.sleep(
1500);
                
uiHandler.post(() -> textView.append("Wynik 2: " + wynik2 + "\n"));
            }
        });

// Zadanie 3
       
executor.execute(new Runnable() {
           
@Override
           
public void run() {
               
// ---- Wątek tła ----
               
double wynik3 = Math.sqrt(12345);
                SystemClock.sleep(
500);
               
uiHandler.post(() -> textView.append("Wynik 3: " + wynik3 + "\n"));
            }
        });
    }

   
@Override
   
protected void onDestroy() {
        
super.onDestroy();
       
// Niezbędne do poprawnego zamknięcia wątków
       
if (executor != null) {
           
executor.shutdown();
        }
}

 

Jeśli zadań będzie więcej niż wątków (czyli 3 w naszym przykładzie) NOWE zadania NIE utworzą nowych wątków. Zostaną umieszczone w kolejce i będą czekać, aż zwolni się któryś z 3 wątków.

Czyli zadania nie zginą, nic się nie wysypie — po prostu będą wykonane później.

 

-----------------------------------------------------------------------------------------------------------------

Przykład użycia ExecutorService + Future + Callable w Androidzie (Java).
To jeden ze sposobów na wykonanie zadania w tle i odebranie wartości zwrotnej.

Przykład: wykonywanie zadań w tle i pobieranie wyników przez Future

 

- Callable - pozwala zwrócić wartość

- Future - odbieramy wynik, gdy zadanie się wykona

- Handler(Looper.getMainLooper()) - bezpieczna aktualizacja UI

 

Future stosujemy zawsze wtedy, gdy chcemy uruchomić zadanie w tle i ODEBRAĆ od niego wynik.
W przeciwieństwie do Runnable, który niczego nie zwraca — Future umożliwia:

To sprawia, że Future jest idealne do zadań, które coś zwracają, np. dane z sieci, wynik obliczeń, treść pliku itd.

 

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MainActivity extends AppCompatActivity {
    ExecutorService
executor;
  

 protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);
        TextView textView = findViewById(R.id.
textView);

       
executor = Executors.newSingleThreadExecutor();
        Handler uiHandler =
new Handler(Looper.getMainLooper());


// Zadanie zwracające wynik
       
Callable<String> zadanie = new Callable<String>() {
            
@Override
           
public String call() throws Exception {
                Thread.sleep(
1500); // symulacja ciężkiej pracy
               
return "Wynik z wątku: " + (10 + 20);
            }
        };

// Wysyłamy zadanie i dostajemy Future
       
Future<String> future = executor.submit(zadanie);


// Osobny wątek sprawdza wynik Future (żeby nie blokować UI)
       
executor.execute(new Runnable() {
           
@Override
           
public void run() {
               
try {
                   
// Blokuje tylko WĄTEK TŁA, nie UI
                   
String wynik = future.get();

                   
// Przekazanie wyniku na wątek główny
                   
uiHandler.post(() -> {
                       
textView.setText(wynik);
                    });

                }
catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
   
@Override
   
protected void onDestroy() {
       
super.onDestroy();
       
// Niezbędne do poprawnego zamknięcia wątków
       
if (executor != null) {
           
executor.shutdown();
        }
}

Wersja z lambda:

 

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class MainActivity extends AppCompatActivity {
    ExecutorService
executor;
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);
        TextView textView = findViewById(R.id.
textView);


       
executor = Executors.newSingleThreadExecutor();
        Handler uiHandler =
new Handler(Looper.getMainLooper());

       
// Lambda dla zadania (Callable)
       
Callable<String> zadanie = () -> {
            Thread.sleep(
1500); // symulacja ciężkiej pracy
           
return "Wynik z wątku (Lambda): " + (10 + 20);
        };

        Future<String> future =
executor.submit(zadanie);

       
// Lambda dla wątku sprawdzającego wynik (Runnable)
       
executor.execute(() -> {
           
try {
                String wynik =
future.get();

               
// Lambda dla Handler.post (aktualizacja UI)
               
uiHandler.post(() -> textView.setText(wynik));

            }
catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

   
@Override
   
protected void onDestroy() {
       
super.onDestroy();
       
// Niezbędne do poprawnego zamknięcia wątków
       
if (executor != null) {
           
executor.shutdown();
        }
    }

 

 

Future używany gdy:

 

1. Zadanie zwraca wartość

Np. pobieranie danych z API, z pliku, z bazy:

 

Future<String> wynik = executor.submit(() -> pobierzDane());

 

2. Chcemy poczekać na wynik — ale nie w UI Thread

 

String dane = future.get(); // oczekiwanie na zakończenie

 

3. Chcemy anulować zadanie

 

future.cancel(true);

 

4. Chcemy sprawdzić, czy zadanie się skończyło

 

if (future.isDone()) { ... }

 

5. Potrzebujemy timeout-u

 

future.get(2, TimeUnit.SECONDS);

 

Jeśli zadanie się nie wykona w 2 sekundy pojawi się wyjątek TimeoutException.

 

Najważniejsza różnica między Future, a Runnable polega na tym że Future gwarantuje wynik a Runntime nie.

 

----------------------------------------------------------------------------------------

Inny sposób na zwracanie wartości i przekazywanie wyniku do wątku głównego. Wykorzystanie klasy CompletableFuture oraz wykorzystanie puli wątków systemowych przez mechanizm ForkJoinPool

 

CompletableFuture
        .supplyAsync(() -> {
           
try {
                Thread.sleep(
1500);
            }
catch (InterruptedException e) {
               
throw new RuntimeException(e);
            }
           
return "Wynik: " + (10 + 20);
        })
        .thenAccept(result ->
                runOnUiThread(() ->
textView.setText(result))
        );

 

To samo bez lambdy:

 

CompletableFuture.supplyAsync(new Supplier<String>() {
   
@Override
   
public String get() {
       
try {
            Thread.sleep(
1500);
        }
catch (InterruptedException e) {
            e.printStackTrace();
        }
       
return "Wynik: " + (10 + 20);
    }
}).thenAccept(
new Consumer<String>() {
   
@Override
   
public void accept(final String result) {

        runOnUiThread(
new Runnable() {
           
@Override
           
public void run() {
               
textView.setText(result);
            }
        });
    }
});

 

----------------------------------------------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------------------------------------------

Przykład wykorzystania klasy  CompletableFuture,  ExecutorService oraz Executors.newFixedThreadPool(3):

 

Poniższy przykład wykorzystuje klasę  CompletableFuture oraz własną pulę wątków zarządzanych przez  ExecutorService.

Jest zalecana metoda programowania wielowątkowego. 

 

package com.example.localdatabaseroom;
import android.os.Bundle;
import android.os.SystemClock;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainActivity extends AppCompatActivity {

   
private TextView textView;
   
// Pula wątków dla 3 równoległych zadań
   
private ExecutorService executor = Executors.newFixedThreadPool(3);

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);

       
textView = findViewById(R.id.textView);
       
textView.setText("--- ROZPOCZĘTO PRACĘ ASYNCHRONICZNĄ (UI PŁYNNE) ---\n");

       
// ZADANIE 1 (1000 ms)
       
CompletableFuture
                .supplyAsync(() -> {
                    SystemClock.sleep(
1000);
                   
return 10 + 20;
                },
executor)
                .thenAccept(wynik ->
                        runOnUiThread(() ->
textView.append("Wynik 1 (1000ms): Liczba: " + wynik + "\n"))
                );

       
// ZADANIE 2 (1500 ms)
       
CompletableFuture
                .supplyAsync(() -> {
                    SystemClock.sleep(
1500);
                   
return "Dane pobrane z serwera";
                },
executor)
                .thenAccept(wynik ->
                        runOnUiThread(() ->
textView.append("Wynik 2 (1500ms): Tekst: " + wynik + "\n"))
                );

       
// ZADANIE 3 (500 ms)
       
CompletableFuture
                .supplyAsync(() -> {
                    SystemClock.sleep(
500);
                   
return Math.sqrt(12345);
                },
executor)
                .thenAccept(wynik ->
                        runOnUiThread(() ->
textView.append("Wynik 3 (500ms): Pierwiastek: " + wynik + "\n"))
                );
    }

   
@Override
   
protected void onDestroy() {
       
super.onDestroy();
       
// ZAWSZE zamykaj pulę wątków, aby uniknąć wycieków pamięci
       
if (executor != null) {
           
executor.shutdown();
        }
    }
}

 

--------------------------------------------------------------------------------------------------------------------------

Poniższy przykład nie jest zalecany dla androida ponieważ wykorzystuje pulę wątków systemowych ForkJoinPool.commonPool.

 

- Nadal używana jest pula wątków, ale jest to pula systemowa, wspólna dla całej maszyny wirtualnej Javy.

- Liczba wątków jest zdeterminowana przez liczbę rdzeni procesora w urządzeniu.

- Tracisz kontrolę nad pulą (nie możesz jej zamknąć, nie wiesz, jakie inne zadania z niej korzystają).

 

Thread (klasa java.lang.Thread) sam w sobie nie jest wątkiem z puli ForkJoinPool.commonPool().

 

1.       Wątek Systemowy (OS Thread / Wątek Natywny)

Wątek systemowy to natywny zasób zarządzany bezpośrednio przez jądro systemu operacyjnego (np. Linux, Windows, Android).

 

2. ExecutorService (Menedżer)

ExecutorService to interfejs. Jest menedżerem, który odrywa Cię od ręcznego tworzenia Thread. Przyjmuje zadania do kolejki i recyklingu już istniejące wątki, zamiast tworzyć je od nowa, co znacznie zwiększa wydajność.

 

3. ForkJoinPool (Specjalista)

To jest wyspecjalizowana implementacja interfejsu ExecutorService. Jest zaprojektowany do:

·   Łamania dużych problemów na małe.

·   Maksymalizacji użycia CPU dzięki algorytmowi "Work Stealing" (bezczynny wątek kradnie pracę od zajętego wątku).

Jest to idealny wybór dla operacji obliczeniowych (np. sortowanie dużych tablic), ale zły dla zadań blokujących (np. operacji sieciowych).

 

 

 

Thread tworzy wątek

ForkJoinPool zarządza grupą wątków, które już istnieją.

 

 

Dlaczego Podajemy Własny ExecutorService

 

Gdy nie podasz własnego ExecutorService (czyli użyjesz tylko supplyAsync(zadanie)), CompletableFuture domyślnie użyje ForkJoinPool.commonPool().

 

Podanie własnego wykonawcy daje Ci kilka kluczowych korzyści i kontroli:

1. Kontrola nad Zasobami (Izolacja)

 

2. Zarządzanie Cyklem Życia

 

3. Wpływ na Nowoczesne Wątki

 

package com.example.localdatabaseroom;

import android.os.Bundle;
import android.os.SystemClock;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.concurrent.CompletableFuture;
// UWAGA: Usunięto importy dla ExecutorService i Executors

public class MainActivity extends AppCompatActivity {

   
private TextView textView;
   
// USUNIĘTO: Definicję ExecutorService

   
@Override
   
protected void onCreate(Bundle savedInstanceState) {
       
super.onCreate(savedInstanceState);
        setContentView(R.layout.
activity_main);

       
textView = findViewById(R.id.textView);
       
textView.setText("--- ROZPOCZĘTO PRACĘ ASYNCHRONICZNĄ (UŻYTO commonPool) ---\n");

       
// ZADANIE 1 (1000 ms)
       
CompletableFuture
               
// supplyAsync BEZ argumentu ExecutorService
               
.supplyAsync(() -> {
                    SystemClock.sleep(
1000);
                   
return 10 + 20;
                })
// Usuń ', executor'
               
.thenAccept(wynik ->
                        runOnUiThread(() ->
textView.append("Wynik 1 (1000ms): Liczba: " + wynik + "\n"))
                );

       
// ZADANIE 2 (1500 ms)
       
CompletableFuture
               
// supplyAsync BEZ argumentu ExecutorService
               
.supplyAsync(() -> {
                    SystemClock.sleep(
1500);
                   
return "Dane pobrane z serwera";
                })
// Usuń ', executor'
               
.thenAccept(wynik ->
                        runOnUiThread(() ->
textView.append("Wynik 2 (1500ms): Tekst: " + wynik + "\n"))
                );

       
// ZADANIE 3 (500 ms)
       
CompletableFuture
               
// supplyAsync BEZ argumentu ExecutorService
               
.supplyAsync(() -> {
                    SystemClock.sleep(
500);
                   
return Math.sqrt(12345);
                })
// Usuń ', executor'
               
.thenAccept(wynik ->
                        runOnUiThread(() ->
textView.append("Wynik 3 (500ms): Pierwiastek: " + wynik + "\n"))
                );
    }

   
// USUNIĘTO: Metodę onDestroy, ponieważ nie ma własnego ExecutorService do zamknięcia.
}

 

----------------------------------------------------------------------------------------------------------------------

4. Zadania

 

Zadanie 1.

Napisz aplikację realizującą funkcje stopera. Wymagane funkcje to:

- start stopera,

- stop stopera,

- kasowanie wyniku.

 

Zadanie 2.

Zaprojektuj i napisz aplikację realizującą funkcje stopera z możliwością pomiaru kilku wyników jednocześnie. Użyj mechanizmu ExecutorService.