Programování

Java 101: Understanding Java threads, Part 1: Introducing threads and runnables

Tento článek je první ze čtyř částí Java 101 série zkoumající vlákna Java. I když si možná myslíte, že vlákna v Javě by bylo obtížné pochopit, chci vám ukázat, že vlákna jsou snadno srozumitelná. V tomto článku vám představím podprocesy Java a spustitelné soubory. V následujících článcích prozkoumáme synchronizaci (prostřednictvím zámků), problémy se synchronizací (například zablokování), mechanismus čekání / upozornění, plánování (s prioritou i bez ní), přerušení podprocesu, časovače, volatilitu, skupiny podprocesů a místní proměnné podprocesu .

Tento článek (součást archivů JavaWorld) byl v květnu 2013 aktualizován o nové výpisy kódů a zdrojový kód ke stažení.

Porozumění vláknům Java - přečtěte si celou sérii

  • Část 1: Představujeme vlákna a spouštěcí tabulky
  • Část 2: Synchronizace
  • Část 3: Plánování vláken a čekání / upozornění
  • Část 4: Skupiny vláken a volatilita

Co je to vlákno?

Koncepčně je pojem a vlákno není těžké pochopit: je to nezávislá cesta provedení prostřednictvím programového kódu. Při spuštění více vláken se cesta jednoho vlákna stejným kódem obvykle liší od ostatních. Předpokládejme například, že jedno vlákno provede ekvivalent bajtového kódu příkazu if-else -li část, zatímco jiné vlákno provede ekvivalent bajtového kódu jiný část. Jak JVM sleduje provádění každého vlákna? JVM dává každému vláknu vlastní zásobník volání metod. Kromě sledování aktuální instrukce bajtového kódu sleduje zásobník volání metody lokální proměnné, parametry, které JVM předává metodě, a návratovou hodnotu metody.

Když více vláken spouští sekvence instrukcí bajtového kódu ve stejném programu, je tato akce známá jako multithreading. Vícevláknové zpracování přináší programu výhody různými způsoby:

  • Programy založené na vícevláknovém grafickém uživatelském rozhraní (grafické uživatelské rozhraní) zůstávají pohotové vůči uživatelům při provádění dalších úkolů, jako je například opětovné vykreslování nebo tisk dokumentu.
  • Vláknové programy obvykle končí rychleji než jejich protějšky bez vláken. To platí zejména pro vlákna běžící na víceprocesorovém počítači, kde každé vlákno má svůj vlastní procesor.

Java dosahuje multithreading prostřednictvím svého java.lang.Thread třída. Každý Vlákno objekt popisuje jedno vlákno provedení. K tomuto provedení dochází v Vláknoje běh() metoda. Protože výchozí běh() metoda nedělá nic, musíte podtřídu Vlákno a přepsat běh() vykonat užitečnou práci. Pro chuť vláken a multithreading v kontextu Vlákno, prozkoumejte Výpis 1:

Výpis 1. ThreadDemo.java

// ThreadDemo.java class ThreadDemo {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); pro (int i = 0; i <50; i ++) System.out.println ("i =" + i + ", i * i =" + i * i); }} class MyThread extends Thread {public void run () {for (int count = 1, row = 1; row <20; row ++, count ++) {for (int i = 0; i <count; i ++) System.out. tisk ('*'); System.out.print ('\ n'); }}}

Výpis 1 představuje zdrojový kód aplikace sestávající z tříd ThreadDemo a MyThread. Třída ThreadDemo řídí aplikaci vytvořením MyThread objekt, spuštění podprocesu, který je přidružen k tomuto objektu, a provedení nějakého kódu pro tisk tabulky čtverců. V porovnání, MyThread přepíše Vláknoje běh() metoda tisku (na standardním výstupním proudu) pravoúhlého trojúhelníku složeného ze znaků hvězdičky.

Plánování vláken a JVM

Většina implementací JVM (pokud ne všechny) využívá podprocesové schopnosti podkladové platformy. Protože tyto funkce jsou specifické pro platformu, může se pořadí výstupu vašich vícevláknových programů lišit od pořadí výstupu někoho jiného. Tento rozdíl vyplývá z plánování, tématu, které prozkoumám později v této sérii.

Když píšete java ThreadDemo ke spuštění aplikace vytvoří JVM počáteční podproces provedení, který provede hlavní() metoda. Provedením mt.start ();, počáteční vlákno říká JVM, aby vytvořil druhé vlákno provádění, které provádí instrukce bajtového kódu obsahující MyThread objektu běh() metoda. Když Start() metoda se vrátí, počáteční vlákno provede své pro smyčka pro tisk tabulky čtverců, zatímco nový podproces provede běh() metoda tisku pravoúhlého trojúhelníku.

Jak vypadá výstup? Běh ThreadDemo zjistit. Všimnete si, že výstup každého vlákna má tendenci se prolínat s výstupem druhého. Výsledkem je, že obě vlákna odesílají svůj výstup do stejného standardního výstupního proudu.

Třída Thread

Abyste se naučili psát vícevláknový kód, musíte nejprve porozumět různým metodám, které tvoří Vlákno třída. Tato část zkoumá mnoho z těchto metod. Konkrétně se dozvíte o metodách pro spouštění vláken, pojmenování vláken, uvedení vláken do režimu spánku, určování, zda je vlákno živé, připojení jednoho vlákna k jinému vláknu a výčet všech aktivních vláken ve skupině vláken a podskupinách aktuálního vlákna. Také diskutuji VláknoLadicí pomůcky a uživatelská vlákna versus vlákna démona.

Zbytek představím Vláknometody v následujících článcích, s výjimkou zastaralých metod společnosti Sun.

Zastaralé metody

Sun zastaral řadu Vlákno metody, jako je pozastavit() a životopis(), protože mohou uzamknout vaše programy nebo poškodit objekty. V důsledku toho byste je neměli volat ve svém kódu. Řešení těchto metod najdete v dokumentaci SDK. V této sérii nepokrývám zastaralé metody.

Konstrukce vláken

Vlákno má osm konstruktérů. Nejjednodušší jsou:

  • Vlákno(), který vytváří a Vlákno objekt s výchozím názvem
  • Vlákno (název řetězce), který vytváří a Vlákno objekt s názvem, který název argument určuje

Další nejjednodušší konstruktory jsou Vlákno (spustitelný cíl) a Thread (Runnable target, String name). Kromě Spustitelný parametry, jsou tyto konstruktory identické s výše uvedenými konstruktory. Rozdíl: Spustitelný parametry identifikují objekty venku Vlákno které poskytují běh() metody. (Dozvíte se o Spustitelný dále v tomto článku.) Poslední čtyři konstruktéři se podobají Vlákno (název řetězce), Vlákno (spustitelný cíl), a Thread (Runnable target, String name); konečné konstruktory však také obsahují a ThreadGroup argument pro organizační účely.

Jeden z posledních čtyř konstruktérů, Thread (ThreadGroup group, Runnable target, String name, long stackSize), je zajímavé tím, že umožňuje určit požadovanou velikost zásobníku podprocesů volání metody. Schopnost specifikovat tuto velikost se v programech s metodami, které využívají rekurzi - techniku ​​provádění, při níž se metoda opakovaně volá -, hodí k elegantnímu řešení určitých problémů. Explicitním nastavením velikosti zásobníku můžete někdy zabránit StackOverflowErrors. Výsledkem však může být příliš velká velikost OutOfMemoryErrors. Sun také považuje velikost zásobníku volání metod za závislou na platformě. V závislosti na platformě se může velikost zásobníku volání metod změnit. Než tedy začnete psát kód, který volá, pečlivě si promyslete důsledky vašeho programu Thread (ThreadGroup group, Runnable target, String name, long stackSize).

Nastartujte svá vozidla

Vlákna připomínají vozidla: pohybují programy od začátku do konce. Vlákno a Vlákno objekty podtřídy nejsou vlákna. Místo toho popisují atributy vlákna, například jeho název, a obsahují kód (pomocí a běh() metoda), kterou vlákno provede. Až přijde čas na spuštění nového vlákna běh(), jiné vlákno volá Vláknonebo jeho podtřídy Start() metoda. Chcete-li například spustit druhé vlákno, počáteční vlákno aplikace - které se spustí hlavní()—Volání Start(). V reakci na to kód pro zpracování vláken JVM pracuje s platformou, aby bylo zajištěno, že se vlákno správně inicializuje a volá a Vláknonebo jeho podtřídy běh() metoda.

Jednou Start() dokončí, provede se více vláken. Protože máme tendenci myslet lineárně, často je pro nás těžké pochopit souběžně (simultánní) aktivita, ke které dochází, když jsou spuštěna dvě nebo více vláken. Proto byste měli prozkoumat graf, který ukazuje, kde se vlákno provádí (jeho pozice) versus čas. Níže uvedený obrázek představuje takový graf.

Graf ukazuje několik významných časových období:

  • Inicializace počátečního vlákna
  • V okamžiku, kdy se toto vlákno začne provádět hlavní()
  • V okamžiku, kdy se toto vlákno začne provádět Start()
  • Moment Start() vytvoří nové vlákno a vrátí se do hlavní()
  • Inicializace nového vlákna
  • V okamžiku, kdy se nové vlákno začne spouštět běh()
  • Různé momenty každé vlákno končí

Všimněte si, že inicializace nového vlákna, jeho provádění běh()a jeho ukončení proběhne současně s provedením spouštěcího vlákna. Všimněte si také, že po volání vlákna Start(), následná volání této metody před běh() metoda ukončí příčinu Start() hodit a java.lang.IllegalThreadStateException objekt.

Co je ve jméně?

Během ladicí relace je užitečné rozlišovat jedno vlákno od druhého uživatelsky přívětivým způsobem. K rozlišení mezi vlákny přidruží Java název k vláknu. Toto jméno je výchozí Vlákno, znak pomlčky a celé číslo od nuly. Můžete přijmout výchozí názvy vláken Java nebo si můžete vybrat vlastní. Pro přizpůsobení vlastních jmen Vlákno poskytuje konstruktory, které berou název argumenty a setName (název řetězce) metoda. Vlákno také poskytuje a getName () metoda, která vrací aktuální název. Výpis 2 ukazuje, jak vytvořit vlastní název pomocí Vlákno (název řetězce) konstruktor a načíst aktuální název v běh() metoda voláním getName ():

Výpis 2. NameThatThread.java

// NameThatThread.java třída NameThatThread {public static void main (String [] args) {MyThread mt; if (args.length == 0) mt = new MyThread (); else mt = new MyThread (args [0]); mt.start (); }} třída MyThread rozšiřuje Thread {MyThread () {// Kompilátor vytvoří ekvivalent bajtového kódu super (); } MyThread (název řetězce) {super (jméno); // Předat jméno do nadtřídy vlákna} public void run () {System.out.println ("Jmenuji se:" + getName ()); }}

Můžete předat volitelný argument názvu MyThread na příkazovém řádku. Například, java NameThatThread X stanoví X jako název vlákna. Pokud nezadáte název, zobrazí se následující výstup:

Jmenuji se: Thread-1

Pokud chcete, můžete změnit super (jméno); zavolat na MyThread (název řetězce) konstruktor na volání setName (název řetězce)-jako v setName (jméno);. Toto druhé volání metody dosahuje stejného cíle - stanovení názvu vlákna - jako super (jméno);. Nechávám to jako cvičení pro vás.

Pojmenování hlavní

Java přiřadí název hlavní na vlákno, které spouští hlavní() metoda, počáteční vlákno. Toto jméno obvykle vidíte v Výjimka ve vlákně „hlavní“ zpráva, že výchozí obslužná rutina výjimky JVM se vytiskne, když počáteční vlákno hodí objekt výjimky.

Spát nebo nespát

Později vám v tomto sloupci představím animace- opakované kreslení na jeden povrch obrazů, které se od sebe mírně liší, aby se dosáhlo pohybové iluze. Aby bylo možné provést animaci, musí se vlákno během zobrazení dvou po sobě následujících obrázků pozastavit. Povolání Vláknoje statický spánek (dlouhé milis) metoda vynutí pozastavení vlákna millis milisekundy. Spící vlákno by mohlo přerušit jiné vlákno. Pokud k tomu dojde, spací nit se probudí a hodí Přerušená výjimka objekt z spánek (dlouhé milis) metoda. Výsledkem je kód, který volá spánek (dlouhé milis) musí se objevit v rámci a Snaž se blok - nebo metoda kódu musí obsahovat Přerušená výjimka v jeho hodí doložka.

Předvést spánek (dlouhé milis), Napsal jsem a CalcPI1 aplikace. Tato aplikace spustí nové vlákno, které používá matematický algoritmus k výpočtu hodnoty matematické konstanty pi. Zatímco nové vlákno počítá, počáteční vlákno se pozastaví na 10 milisekund voláním spánek (dlouhé milis). Po probuzení výchozího vlákna vytiskne hodnotu pí, kterou nové vlákno ukládá do proměnné pi. Seznam 3 dárků CalcPI1zdrojový kód:

Výpis 3. CalcPI1.java

// CalcPI1.java třída CalcPI1 {public static void main (String [] args) {MyThread mt = new MyThread (); mt.start (); zkuste {Thread.sleep (10); // Spánek po dobu 10 milisekund} catch (InterruptedException e) {} System.out.println ("pi =" + mt.pi); }} třída MyThread rozšiřuje Thread {boolean negative = true; dvojité pí; // Inicializuje se na 0,0, ve výchozím nastavení public void run () {for (int i = 3; i <100000; i + = 2) {if (negative) pi - = (1,0 / i); else pi + = (1,0 / i); negativní =! negativní; } pi + = 1,0; pi * = 4,0; System.out.println ("Hotový výpočet PI"); }}

Pokud spustíte tento program, uvidíte výstup podobný (ale pravděpodobně ne identický) následujícímu:

pi = -0,2146197014017295 Hotový výpočet PI
$config[zx-auto] not found$config[zx-overlay] not found