270 likes | 420 Views
Franti šek Galčík. Konkurentn é programovanie v JAVE. Príklad na úvod. Jednoduchá GUI aplikácia Dlho trvajúca akcia blokuje hlavný „tok vykonávania príkazov“ v programe: pomalé I /O, sie ťová komunikácia (download z Internetu) d lho trvajúci výpočet
E N D
František Galčík Konkurentnéprogramovaniev JAVE
Príklad na úvod • Jednoduchá GUI aplikácia • Dlho trvajúca akcia blokuje hlavný „tok vykonávania príkazov“ v programe: • pomalé I/O, sieťová komunikácia (download z Internetu) • dlho trvajúci výpočet • Jednoduché riešenie: Vytvoriť “podproces” súbežne bežiaci s hlavným programom, ktorý bude zdieľať s hlavným programom haldu (“svet, v ktorom žijú objekty”)
Vlákno - Thread • Simultálne bežiaca podúloha • Každá aplikácia má aspoň jedno vlákno, ktoré sa vytvorí pri štarte aplikácie a v ktorom sa začne vykonávať „main“
Vytvorenie vlákna • Vlákna môžu vytvárať nové vlákna • Kľúčová trieda:java.lang.Thread • 2 základné spôsoby vytvorenia vlákna: • Rozšírením triedy Thread a prekrytím metódy run • Implementovaním rozhrania Runnable • Akcie, ktoré má (súbežne) vykonať vlákno sú definované v metóde run() • Po vykonaní metódy run() vlákno prestáva existovať(objekt triedy Thread reprezentujúci vlákno však môže existovať ďalej) • Vykonávanie vlákna sa štartuje metódou start()
Príklad vytvorenia vlákna • Vytvorenie vlákna rozšírením triedy Thread: • Vytvoríme vlákno, ktoré bude zadaný čas vypisovať určený text pomocou objektu „vypisovača“ • Vytvorenie vlákna implementovaním rozhrania Runnable • Pozastavenievykonávania vlákna na stavovený čas: • Thread.sleep(…);
Čo nám ponúka trieda Thread • Vlákno patrí do nejakej ThreadGroup • Skupinové riadenie vlákien • Vlákno môže pristupovať len k vláknam zo svojej ThreadGroup a jeho podskupín • Stromová hierarchia skupín • 2 druhy vlákien: • Používateľské (User) • Daemon(setDaemon, isDaemon) • Aplikácia končí, keď skončia všetky užívateľské vlákna (za daemonmi sa nečaká)
Čo nám ponúka trieda Thread • Riadenie priority vlákna (setPriority/getPriority): • 1 najnižšia, 5 default, 10 najvyššia • Nastavenie ClassLoadera pre dané vlákno • Statická metóda Thread.currentThread() • Vráti referenciu na objekty triedy Thread, reprezentujúci bežiace vlákno • Užitočné pre vlákna vzniknuté implementovaním Runnable
Ako predčasne ukončiť vlákno ? • Java neponúka možnosť predčasného ukončenia vlákna • Je možné poslať vláknu požiadavku na ukončenie: metóda interrupt() • Vlákno informáciu o požiadavke zistí: • Získaním informácie metódou isInterrupted() alebo interrupted() • Vznikom výnimky InterruptedException počas operácií, kedy je vlákno „uspané“
Kooperácia medzi vláknami • Zasielanie požiadaviek (nastavenie príznaku) na ukončenia • Metóda t.join(): pozastaví vykonávanie vlákna, až kým vlákno t neskončí • Príklad: Ako to vyzerá, keď vlákna nefungujú tak, ako majú....
Vlákien je veľa a procesorov málo • V určitom okamihu môže bežať najviac toľko vlákien, koľko máme k dispozícii procesorov • Ilúzia súbežného behu: time-slicing
Vlákna a pamäť • Každé vlákno: • má svoj vlastný stack, kde sú uložené lokálne premenné • zdieľa heap, kde sú vytvárané objekty
Monitory • Monitory zabezpečujú pre vlákna: • vzájomné vylúčenie(mutual exclusion) • kooperáciu • S každým objektom je asociovaný monitor • Monitor kontroluje vykonávanie istých častí kódu: kritických sekcií asociovaných s monitorom • V každom okamihu môže vlastniť monitor (vykonávať kód kritickej sekcie monitora)nanajvýš jedno vlákno
Monitory v kóde synchronized (objekt) { .... kód kritickej sekcie monitora asociovaného s objektom objekt ... } • Ak chceme synchronizovať celý kód metódy voči monitoru asociovanému objektu this, stačí pridať „synchronized“ ku hlavičke inštančnej metódy
Problém producenta a konzumenta • Sklad s obmedzenou kapacitou • Producent produkuje položky a vkladá ich do skladu, ak je v sklade voľné miesto • Konzument vyberá zo skladu a konzumuje, ak je v sklade nejaká položka Riešenie bez kooperácie: • Vlákno producent a konzumer sú synchronizovanémonitorom asociovaným s objektom skladu: v každom okamihu je iba jeden z nich vo vnútri skladu • Ak sa sklad vyprázdni, konzument musí pravidelne zazerať do skladu, či je ešte stále prázdny – činnéčakanie na príchod novej položky
Kooperácia vlákien • Podpora kooperácie (eliminácia činného čakania na splnenie podmienky): • wait() – vlákno v kritickej sekcii sa vzdá monitora dovtedy, kým od iného vlákna nepríjme notifikáciu • notify(), notifyAll() – vlákno v kritickej sekcii oznáminejakému čakajúcemu vláknu, resp. čakajúcim vláknam, že môžu v kritickej sekcii pokračovať • wait(), notify(), notifyAll() sú inštančné metódy objektu, s ktorým je asociovaný monitor
Algoritmus konzumenta while (true) { synchronized (sklad) { // Kym niet v sklade ziadnej polozky // cakame za notifikaciou while (!sklad.hasItem()) sklad.wait(); // Skonzumujeme polozku consume(sklad.getItem()); // Ak niekto (producent) caka za uvolnenim // miesta v sklade, tak mu posleme notifikaciu sklad.notify(); } }
Volatile premenné • volatile označuje premenné, ktoré môžu byť modifikované viacerými vláknami a inštruuje procesor, aby vždy použil hodnotu „aktuálnu“ hodnotu namiesto hodnoty cachovanej v registroch public void doWork() { while (!shutdownRequested) processing(); }
Vlákna v JVM • “Green Threads” • do verzie 1.1 jediný mechanizmus riadenia vlákien • kooperatívny multitasking • JVM simuluje viacero vlákien v jednom vlákne • Od verzie 1.2 využívanie vlákien OS • Monitory: • Inštrukcie v byte-code(monitorenter, monitorexit) • Ku každému objektu je priradené počítadlo počítajúce počet vstupov vlákna do kritickej sekcie asociovaného monitora
Ďalšie užitočné triedy • ThreadLocal– objekt, ktorý pre každé vlákno uchováva špecifickú hodnotu • Timer • vytvára vlákno, do ktorého je možné vkladať úlohy ako objekty triedy TimerTask • vykonanie úlohy je možné naplánovať na určitý čas, resp. periodické vykonávanie • SwingTimer, SwingWorker– mechanizmus na podporu konkurencie v rámci Swing a vykonávania akcií v rámci Event Dispatch Threadu (jediný spôsob ako pracovať thread-safety so Swing-om)
Java 1.5: Executor Framework • Problém: Ak potrebujeme vykonávať veľa krátkych súbežných úloh, tak vytvorenie vlákna sa stane drahou operáciou • Riešenie: Budeme udržiavať „pool” vlákien a ak príde nová úloha, tak použijeme nejaké voľné vlákno • Executors: továreň Executorov • newSingleThreadExecutor() • newFixedThreadPool() • newCachedThreadPool() • newScheduledThreadPool()
publicclass WebServer { static Executor pool = Executors.newFixedThreadPool(7); publicstaticvoid main(String[] args) { ServerSocket socket = new ServerSocket(80); while (true) { final Socket connection = socket.accept(); Runnable r = new Runnable() { publicvoid run() { handleRequest(connection); } }; pool.execute(r); } } }
Java 1.5: Callable, Future • Problém: neexistuje jednoduchý spôsob ako vytvoriť súbežnú úlohu, ktorá vráti výsledok iným vláknam • Riešenie: • Executor má metódu submit, ktorou ide odoslať na vykonanie úlohu • Úloha je preprezentovaná ako implementácia rozhrania Callable<V>… metóda V call() • Výsledkom volania submit je implementácia rozhrania Future<V>, ktoré poskytuje metódu get() na získanie výsledku vykonania úlohy
Java 1.5: Lepšia synchronizácia • Problém: systém monitorov je obmedzený, dovoľuje len hierarchické získavanie monitorov (zámkov) • Riešenie: • Semaphore: acquire/release • java.util.locks: Lock, Condition, ReadWriteLock, ReentrantLock, ReentrantReadWriteLock • Lock: lock/unlock • Synchronizované kolekcie: ArrayBlockingQueue • Atomické premenné
Java 1.5: Lepšia koordinácia • CountDownLatch: umožňuje pozastaviť vlákna, kým sa nevykoná určitá množina operácií (countDown, await) • CyclicBarrier: umožnuje pozastaviť vlákna, kým zadaný počet vlákien nedosiahne definovanú bariéru (await)