Search
Vlákna přináší možnost multitaskového prostředí do Javy. Multitasking znamená možnost “souběžné” práce několika procesů. Slouží ke zrychlení programu, který nemusí čekat na ukončení dlouhých operací typu přístupu na síť a může sbírat další potřebné informace k výpočtu a výpočet provést až vlákno zpracuje požadavek.
Nejjednodušší práce s vlákny je přes třídu Thread. Nejdůležitější metodou této třídy je metoda run(), která specifikuje chování vlákna jako takového.
Zde je uveden příslušný kód:
/** * Jednoduché vlakno */ public class JednoducheVlakno extends Thread { private int _count = 5; /** * Konstruktor musi obsahovat start() */ public JednoducheVlakno() { super("Jmeno Vlakna"); start();// Ostartuje beh vlakna } /** * Telo vlakna */ public void run() { while( true ) { System.out.println(_count); if (_count-- == 0) return; } } }
Problémy:
Lepší implementace je pomocí rozhraní Runnable.
public class Vlakno implements Runnable{ int sdilena = 0; public void run() { for(int i = 0; i<100;i++){ System.out.printf("Sdilena:%5d, i:%5d%n", sdilena++,i); //System.out.println("Sdilena:" + sdilena + " i: " +i); } } }
public class TestSdileni { public static void main(String[] args) { Vlakno v1 = new Vlakno(); Thread t1 = new Thread(v1), t2 = new Thread(v1); //obě vlákna musí být nad stejným objektem typu Runnable - zde v1 t2.start(); t1.start(); } }
Zkuste spustit předchozí příklad. Jeden z možných výstupů je tento:
Sdilena: 0, i: 0 Sdilena: 2, i: 1 Sdilena: 3, i: 2 Sdilena: 4, i: 3 Sdilena: 5, i: 4 Sdilena: 6, i: 5 Sdilena: 7, i: 6 Sdilena: 8, i: 7 Sdilena: 9, i: 8 Sdilena: 10, i: 9 Sdilena: 11, i: 10 Sdilena: 12, i: 11 Sdilena: 13, i: 12 Sdilena: 14, i: 13 Sdilena: 15, i: 14 Sdilena: 16, i: 15 Sdilena: 17, i: 16 Sdilena: 1, i: 0 Sdilena: 19, i: 1 Sdilena: 20, i: 2
Vlákno skončí svoji činnost, pokud je ukončena metoda run(). Vlákna sdílejí společný procesorový čas a tak pokud běží na jedno-procesorovém počítači jedno vlákno jsou ostatní vlákna suspendována a čekají na přidělení CPU. Přidělování procesoru řídí plánovač úloh(Scheduler) a aby vlákno nezabíralo příliš času na CPU lze informovat plánovač statickou metodou yeild() o tom, že vlákno dospělo do částečného výpočtu a může být suspendováno. Volání této metody samozřejmě automaticky nevede na přerušení činnosti vlákna, ale zvyšuje pravděpodobnost této akce.
Je nutné uvést, že plánovač je zde míněn na úrovni JVM, tedy, aby vlákno běželo, musí běžet (být naplánována) JVM a v ní musí být toto vlákno naplánováno. Tedy všechna vlákna v rámci jednoho programu sdílí jeden čas přidělovaný OS.
Další důležitou metodu, kterou přináší třída Thread je metoda sleep(), která přeruší činnost vlákna na dobu specifikovanou argumentem této funkce. Časovou jednotkou argumentu je milisekunda.
try { sleep( 100 ); } catch ( InterruptedExeception e) { //... Osetreni vyjimky peruseni }
Priorita je určena číselnou hodnotou a výše této hodnoty určuje, jak často dostane vlákno přiděleno procesorový čas. Java má 10 priorit, ale jsou zde problémy s multiplatfomností, jelikož třeba OS Windows má pouze 7 priorit a tak se provádí rekalkulace. Jediné bezpečné použití priorit přináší konstanty MAX_PRIORITY, MIN_PRIORITY a NORM_PRIORITY. Samotné nastavení se provádí metodou třídy Thread setPriority( int ). Aktuální hodnota priority se dá získat metodou int getPriority().
Každé vlákno existuje jen po dobu existence svého rodiče a ten se neukončí, dokud alespoň jedno nedémonické vlákno běží. Pokud chceme, aby se hlavní program ukončil nezávisle na nějakém vlákně, tak toto vlákno nastavíme jako Deamona. To se provádí metodou setDeamon( boolean ), která musí být zavolána před spuštění metody start(). Všechny vlákna vytvořená Deamonem jsou také deamoni a pokud chci o nějakém vláknu zjistit, jestli je deamonem, tak použiji metodu isDeamon().
Do provádění vláken lze zavést jistý pořádek a tak dosáhnout určité návaznosti. Pokud nějaké vlákno potřebuje výsledné informace, které zpracovává jiné vlákno, tak se pomocí metody join( jineVlakno ) uspí, dokud není vlákno zpracovávající informaci ukončeno. Informaci o ukončeni vlákna lze přečíst metodou isAlive().
Pokud se může stát, že bude k jednomu objektu přistupovat více vláken s různými metodami měnícími její obsah, je třeba zaručit konzistenci objektu mezi jednotlivými operacemi tj. exklusivitu práce s objektem. Aby byl přístup exkluzivní, tak se u objektů zavádí zámek, kterým si přistupující vlákno zamkne objekt před interferencí s ostatními. Pokud chce vlákno pracovat se zamčeným objektem, tak je odloženo do uvolnění objektu. Tento zámek se zavádí u metod pracující s kritickým objektem klíčovým slovem synchronized.
/** * pouziti synchronized */ public class SynchronizedEvenGen { int i = 0; synchronized void next() {i++; i++;} synchronized int getVal() { return i;} .... }
Nejen metody lze synchronizovat, protože některé operace mohou být velice rozsáhlé a tak lze vytvářet i tzv. kritické sekce, kde se vyskytují operace se sdíleným objektem. Do těchto sekcí lze vstoupit jen tehdy, pokud získáme klíč od specifikovaného objektu.
/** * pouziti kriticke sekce */ public void velkaMetoda() { ... synchronized (synObject) { synObject.op1(); } .... }
Zámek kritické sekce se uvolní:
Metody:
Všechny tyto metody lze použít pouze za předpokladu, že objekt vlastní klíč od příslušného vlákna, tedy uvnitř synchronizovaného bloku nebo metody.
Žádost o uzamčení další kritické sekce bez odemčení předchozí může vést k nebezpečné situaci, kdy jsou všechna vlákna podílející se na přístupu k těmto sdíleným zdrojům pozastavena (blokována čekáním na uvolnění některého sdíleného prostředku) a proces se zablokuje. Bez dodatečných opatření se vlákna ze zablokovaného stavu sama nemohou uvolnit.
Ochrana před uváznutím - zachovat shodné pořadí žádostí o sdílené prostředky ve všech zúčastněných vláknech
Jedno vlákno produkuje data (producent) a druhé vlákno tato data spotřebovává (konzument).
Konzument nemůže spotřebovat více dat než producent vyprodukuje.
V případě, že jsou všechna právě vyprodukovaná data spotřebována musí konzument čekat. Naopak producent může vyprodukovat jen tolik dat, kolik je konzument schopen spotřebovat (včetně obsahu případné vyrovnávací paměti). Pokud není vyprodukovaná data kam ukládat musí producent čekat, dokud konzument data nespotřebuje.
public class Main { public static void main(String[] args) { Vycep s = new Vycep(10); Producent p1 = new Producent(s,1); Konzument k1 = new Konzument(s,1); Thread t1 = new Thread(p1); Thread t2 = new Thread(k1); t1.start(); t2.start(); try { // necham bezet 10 sec Thread.sleep(10 * 1000); } catch (InterruptedException ex) { System.out.println("Main: Doslo k preruseni."); } s.setVycepOtevren(false); try { t1.join(); t2.join(); } catch (InterruptedException ex) { System.out.println("Main: Doslo k preruseni."); } System.out.println("Program ukoncen"); } }
public class Producent implements Runnable { private Vycep s; private int counter; private int cislo; public Producent(Vycep s, int cislo) { this.s = s; this.cislo = cislo; counter = 0; } public void run() { while (s.isVycepOtevren()) { int polozka = counter++; System.out.println("Vycepni" + cislo + ": Chysta se tocit " + polozka + ". pivo."); s.put(polozka); Thread.yield(); } } }
public class Konzument implements Runnable { private Vycep s; private int cislo; public Konzument(Vycep s, int cislo) { this.s = s; this.cislo = cislo; } public void run() { while (s.isVycepOtevren()) { System.out.println("Konzument" + cislo + ": Vypil pivo " + s.get()); Thread.yield(); } } }
public class Vycep { private int items[]; private int top, bottom; private boolean full; private boolean vycepOtevren; public Vycep(int size) { items = new int[size]; top = bottom = 0; vycepOtevren = true; } synchronized public void put(int item) { try { while (full && isVycepOtevren()) { System.out.println("Dosly sklenice, cekam..."); wait(); } items[top] = item; System.out.println("Natoceno pivo " + item); top++; top %= items.length; if (top == bottom) full = true; notifyAll(); } catch (InterruptedException e) { System.out.println("Vycep: Doslo k preruseni."); } } synchronized public int get() { int result = -1; try { while(top == bottom && !full && isVycepOtevren()) // empty { System.out.println("Doslo pivo, cekam..."); wait(); } result = items[bottom]; System.out.println("Zakaznik vzal pivo " + result); bottom++; bottom %= items.length; full = false; notifyAll(); } catch (InterruptedException e) { System.out.println("Vycep: Doslo k preruseni."); } return result; } synchronized public boolean isVycepOtevren() { return vycepOtevren; } synchronized public void setVycepOtevren(boolean vycepOtevren) { this.vycepOtevren = vycepOtevren; notifyAll(); } }
Šablonu pro úkol typu „výčep“ s implementovanou kruhovou frontou si můžete stáhnout zde.