Programovací jazyk Java
8. přednáška
Obsah
Vlákna
- Vlákna představují způsob, jak v rámci jednoho procesu provádět více činností paralelně (na počítači s více procesory nebo
s vícejádrovým procesorem) nebo pseudoparalelně (na počítači s jedním jednojádrovým procesorem).
- Vlákno je posloupností po sobě jdoucích operací (příkazů, instrukcí, ...). Každý proces je tvořen nejméně jedním vláknem.
V rámci každého z vláken je vykonáván kód nezávisle na ostatních vláknech.
- Java přímo podporuje vícevláknový běh programu. Vícevláknové programování má smysl například u těchto typů úloh:
- u časově náročných výpočtů, kdy jedno vlákno provádí výpočet a druhé vlákno může průběžně informovat uživatele o stavu výpočtu,
- za účelem využití času stráveného čekáním na vstup od uživatele,
- pokud program simuluje aktivitu více entit, např. pohyb molekul plynu,
- u klient–server aplikací, kde bývá často pro obsluhu každého připojeného klienta vytvořeno samostatné vlákno,
- v úlohách typu producent–konzument, kde producent připravuje data, která konzument spotřebovává.
- Vlákna mají oproti procesům řadu výhod:
- Vlákno se vytvoří rychleji než proces.
- Vlákno se ukončí rychleji než proces.
- Mezi vlákny se rychleji přepíná než mezi procesy.
- Lze dosáhnout lepší strukturalizace programu.
- Vlákno lze vytvořit děděním z třídy Thread nebo implementací rozhraní Runnable.
Třída Thread
- Vlákno lze vytvořit jako instanci třídy java.lang.Thread nebo její podtřídy.
- V nejjednodušším případě stačí odvodit potomka od třídy Thread a překrýt klíčovou metodu run(), která popisuje, co
vlákno při svém běhu vlastně dělá. Metoda run() vlákna se nespouští přímo, ale pomocí volání metody start() zděděné ze
třídy Thread.
- Příklad:
public class Vlakno extends Thread {
public Vlakno(String jmeno) {
super(jmeno);
}
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(i + ". " + getName());
try {
Thread.sleep(1000);
}
catch(InterruptedException e) {
System.out.println("Jsem vzhuru - " + getName());
}
}
}
public static void main(String[] args) {
Vlakno vl = new Vlakno("Pepa");
vl.start();
new Vlakno("Karel").start();
new Vlakno("Zdena").start();
}
}
- Poznámka: Metoda getName() vrací název konkrétní instance vlákna.
Metody pro práci s vlákny
- Metoda Thread.State getState() vrací aktuální stav vlákna (NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED).
- Metodou static void yield() může vlákno nabídnout předání řízení jinému vláknu.
- Metodou static void sleep(long millis) lze vlákno uspat na zadaný počet milisekund.
- Metodou void wait() lze vlákno pozastavit do doby, kdy bude probuzeno metodou void notify() nebo void notifyAll(),
příp. přerušeno metodou void interrupt().
- Metodou boolean isAlive() lze o jiném vláknu zjistit, jestli je „naživu“, tzn. mezi spuštěním metodou start() a
ukončením metody run().
- Pokud někdy potřebujeme počkat na doběhnutí nějakého vlákna, můžeme voláním metody void join() jiného vlákna pozastavit běh metody
do doby, než dokončí svou práci vlákno, u něhož je metoda volána. Existuje i varianta void join(long millis), která čeká
maximálně po stanovenou dobu.
Metoda void interrupt()
- Přerušením oznamujeme vláknu, že by mělo přestat vykonávat svoji běžnou činnost a udělat něco jiného. je na programátorovi, aby rozhodl, jak bude vlákno reagovat na přerušení,
ale je obvyklé ukončit činnost vlákna (tzn. metody run()).
- Metoda vyvolá výjimku InterruptedException, kterou je třeba ošetřit v bloku catch.
Stavy vlákna
- Každé vlákno se v každém okamžiku nachází v jednom z pěti možných stavů. Přechody mezi těmito stavy zajišťují nejčastěji metody
třídy Thread.

Obrázek 1: Možné stavy vlákna
- Stavy vláken jsou:
- Nové vlákno – vlákno bylo vytvořeno (pomocí new), ale dosud nebylo spuštěno
metodou start().
- Běhuschopné vlákno – metoda start() již proběhla. Těchto vláken může být víc, ale pouze jedno
je běžící, ostatní čekají na předání řízení.
- Běhuneschopné – vlákno, které:
- bylo uspáno metodou sleep(),
- čeká v metodě wait() na probuzení nebo
- čeká na I/O.
- Mrtvé vlákno – vlákno, jehož metoda run() skončila.
Rozhraní Runnable
- Implementací tohoto rozhraní můžeme zařídit, aby výhody vlákna mohla využívat i třída, která vznikla děděním od jiné třídy než Thread
nebo jejího dědice.
- Rozhraní Runnable obsahuje pouze jednu metodu – run(), kterou musíme implementovat.
- Vlákno pak vytvoříme jako instanci třídy Thread, kdy parametrem konstruktoru bude odkaz na instanci třídy implementující
Runnable.
- Příklad
class VlaknoNedediThread implements Runnable {
public void run() {
// kód vlákna
}
}
class ThreadUser {
public static void main(String args[]) {
Thread t = new Thread(new VlaknoNedediThread());
t.start();
}
}
Příklad: Spolupráce dvou vláken: první vlákno vypíše na std. výstup slovo START, pak spustí druhé vlákno a po jeho skončení vypíše STOP. Druhé vlákno vypíše v třísekundových intervalech čísla 1, 2 a 3.
public class Vlakno123 implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println("Jsem vzhuru!");
}
System.out.println(i);
}
}
}
public class StartStop extends Thread {
Thread vlakno123;
public StartStop(Runnable vlakno123) {
this.vlakno123 = new Thread(vlakno123);
}
@Override
public void run() {
System.out.println("START");
vlakno123.start();
try {
vlakno123.join();
} catch (InterruptedException ex) {
System.out.println("Čekání na dokončení vlákna 123 přerušeno.");
}
System.out.println("STOP");
}
public static void main(String[] args) {
Vlakno123 v123 = new Vlakno123();
new StartStop(v123).start();
}
}
Priorita vlákna
- Každé vlákno má prioritu, podle které je mu předáváno řízení (tj. poskytovány systémové prostředky). To znamená, že pokud jsou běhuschopná
dvě vlákna, bude vždy předáno řízení vláknu s vyšší prioritou.
- Priorita vlákna se dá zjistit metodou int getPriority() a nastavit metodou void setPriority(int newPriority).
Změnit prioritu vlákna lze před i po jeho spuštění metodou start().
- Nejnižší priorita má hodnotu MIN_PRIORITY, nejvyšší MAX_PRIORITY, normální je NORM_PRIORITY, což je
priorita standardně přidělená nově vznikajícím vláknům. Lze použít i čísla v rozsahu od 1 do 10, přičemž platí:
- MIN_PRIORITY = 1,
- NORM_PRIORITY = 5,
- MAX_PRIORITY = 10.
Vlákna typu démon
- Pokud program používá běžná vlákna, nemůže skončit dříve, než jsou ukončena všechna vlákna (tzn. jejich metody run()).
- Někdy však vlákno slouží pouze k obsluze určitých požadavků a jeho existence po skončení ostatních vláken je nepotřebná.
- Pokud vlákno označíme jako démona, program skončí bez ohledu na to, zda již vlákno doběhlo či nikoliv.
- Označení za démona se provádí metodou void setDaemon(boolean) s parametrem true (před startem vlákna).
- Metodou boolean isDaemon() lze zjistit, zda je vlákno typu démon.
Monitor, synchronizace
- Problém konzistence sdílených dat vláken:
- Vlákna jednoho procesu sdílí paměť a soubory, a tudíž mohou mezi sebou komunikovat, aniž by k tomu potřebovaly služby jádra.
- Vlákna jedné aplikace se proto musí mezi sebou synchronizovat, aby se zachovala konzistentnost zpracovávaných dat.
- Příklad:
public void setData(int x, int y) {
this.x = x;
this.y = y;
}
public int[] getData() {
return new int[]{x, y};
}
- Pokud jedno vlákno bude volat metodu setData a druhé vlákno metodu getData, mohlo by se stát, že prvnímu vláknu bude mezi
dvěma příkazy metody setData odebráno řízení a druhé vlákno pak přečte nekonzistentní dvojici čísel (jedno staré + jedno nové).
- Tomu lze předejít použitím monitoru.
- Monitor je synchronizační prvek, které se používá pro řízení přístupu ke sdíleným prostředkům.
- Monitor se skládá z dat, ke kterým je potřeba řídit přístup, a množiny funkcí, které nad těmito daty operují.
- V Javě má každý objekt automaticky přiřazen svůj monitor. Funkce, které patří do monitoru, jsou označeny pomocí klíčového
slova synchronized. Možnosti použití tohoto klíčového slova jsou následující:
- Pokud je tímto klíčovým slovem označena metoda, je zahrnuta do monitoru každé instance své třídy.
- Pomocí bloku synchronized(obj) {...} lze označit část metody, která bude patřit do monitoru objektu obj.
- Příklad synchronizovaných metod:
public synchronized void setData(int x, int y) {
this.x = x;
this.y = y;
}
public synchronized int[] getData() {
return new int[]{x, y};
}
- Synchronizace může způsobit uváznutí (deadlock).
- Podrobněji o vláknech i synchronizaci ZDE.
The End