Bevezetés
A Multithreading a kezdetektől fogva beépült a Java-ba. Tehát, nézzük röviden ezt a dolgot, amit többszálúnak neveznek.
![Jobb együtt: Java és a Thread osztály. I. rész – A végrehajtás szálai – 1]()
Tájékoztatásul az Oracle hivatalos leckét vesszük: "
Lecke: A "Hello World!" alkalmazás ". Kissé módosítjuk Hello World programunk kódját az alábbiak szerint:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args
a program indításakor átadott bemeneti paraméterek tömbje. Mentse ezt a kódot egy olyan fájlba, amelynek neve megegyezik az osztály nevével, és amelynek kiterjesztése
.java
. Fordítsa le a
javac segédprogrammal:
javac HelloWorldApp.java
. Ezután futtatjuk a kódunkat valamilyen paraméterrel, például "Roger":
java HelloWorldApp Roger
![Jobb együtt: Java és a Thread osztály. I. rész – A végrehajtás szálai – 2]()
A kódunknak jelenleg komoly hibája van. Ha nem ad át egyetlen argumentumot sem (vagyis csak a "java HelloWorldApp" parancsot hajtja végre), akkor hibaüzenetet kapunk:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
Kivétel (pl. hiba) történt a "main" nevű szálban. Szóval, a Java-nak vannak szálai? Itt kezdődik a mi utunk.
Java és szálak
Ahhoz, hogy megértsük, mi a szál, meg kell értenünk, hogyan indul el egy Java program. Változtassuk meg a kódunkat az alábbiak szerint:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
// Do nothing
}
}
}
Most fordítsuk újra a -val
javac
. A kényelem kedvéért a Java kódunkat egy külön ablakban futtatjuk. Windows rendszeren ez a következőképpen tehető meg:
start java HelloWorldApp
.
Most a jps segédprogramot fogjuk használni , hogy megnézzük, milyen információkat tud közölni a Java:
![Jobb együtt: Java és a Thread osztály. I. rész – A végrehajtás szálai – 3]()
Az első szám a PID vagy a folyamatazonosító. Mi az a folyamat?
A process is a combination of code and data sharing a common virtual address space.
A folyamatok során a különböző programok futásuk során elkülönülnek egymástól: minden alkalmazás a saját memóriaterületét használja anélkül, hogy más programokat zavarna. Ha többet szeretne megtudni, azt javaslom, hogy olvassa el ezt az oktatóanyagot:
Folyamatok és szálak . Egy folyamat nem létezhet szál nélkül, tehát ha létezik folyamat, akkor annak legalább egy szála van. De hogyan jön ez elő a Java-ban? Amikor elindítunk egy Java programot, a végrehajtás a metódussal kezdődik
main
. Mintha belelépnénk a programba, ezért ezt a speciális
main
módszert nevezzük belépési pontnak. A
main
metódusnak mindig "public static void"-nak kell lennie, hogy a Java virtuális gép (JVM) elkezdhesse végrehajtani programunkat. További információ:
Miért statikus a Java fő metódus?. Kiderült, hogy a Java indító (java.exe vagy javaw.exe) egy egyszerű C-alkalmazás: betölti a különböző DLL-eket, amelyek valójában a JVM-et tartalmazzák. A Java indító a Java Native Interface (JNI) hívások meghatározott készletét hajtja végre. A JNI egy olyan mechanizmus, amely összekapcsolja a Java virtuális gép világát a C++ világával. Tehát az indító nem maga a JVM, hanem egy mechanizmus a betöltésére. Ismeri a végrehajtandó helyes parancsokat a JVM elindításához. Tudja, hogyan kell használni a JNI-hívásokat a szükséges környezet beállításához. Ennek a környezetnek a beállítása magában foglalja a fő szál létrehozását, amelyet "fő"-nek neveznek.
A Java folyamatban lévő szálak jobb szemléltetésére a jvisualvm-et használjuk
eszköz, amely a JDK-hoz tartozik. Egy folyamat pid-jének ismeretében azonnal láthatjuk az adott folyamatra vonatkozó információkat:
jvisualvm --openpid <process id>
![Jobb együtt: Java és a Thread osztály. I. rész – A végrehajtás szálai – 4]()
Érdekes módon minden szálnak megvan a saját külön területe a folyamat számára lefoglalt memóriában. Ezt a memóriastruktúrát veremnek nevezzük. Egy köteg keretekből áll. A keret egy metódus aktiválását jelöli (egy befejezetlen metódushívás). Egy keret StackTraceElementként is ábrázolható (lásd a
StackTraceElement Java API-ját ). Az egyes szálakhoz lefoglalt memóriáról itt találhat további információt: "
Hogyan foglal le a Java (JVM) verem minden szálhoz ". Ha megnézi a
Java API-t , és rákeres a "Thread" szóra, a java.lang.Thread fájlt találja.
osztály. Ez az az osztály, amely egy szálat képvisel Java-ban, és ezzel kell dolgoznunk.
java.lang.Szál
Java nyelven egy szálat az osztály egy példánya képvisel
java.lang.Thread
. Azonnal meg kell értenie, hogy a Thread osztály példányai önmagukban nem végrehajtási szálak. Ez csak egyfajta API a JVM és az operációs rendszer által kezelt alacsony szintű szálakhoz. Amikor elindítjuk a JVM-et a Java indító segítségével, létrehoz egy
main
"fő" nevű szálat és néhány további háztartási szálat. Ahogy azt a JavaDoc a Thread osztályhoz tartalmazza:
When a Java Virtual Machine starts up, there is usually a single non-daemon thread
. Kétféle szál létezik: démonok és nem démonok. A démonszálak háttér (háztartási) szálak, amelyek a háttérben dolgoznak. A "démon" szó Maxwell démonára utal.
Ebben a Wikipédia cikkben többet megtudhat . Ahogy a dokumentációban is szerepel, a JVM mindaddig folytatja a program (folyamat) végrehajtását, amíg:
- A Runtime.exit() metódus meghívásra kerül
- Minden NEM démon szál befejezi a munkáját (hiba nélkül vagy kivételekkel)
Ebből egy fontos részlet következik: a démonszálak bármikor megszakíthatók. Ennek eredményeként nincs garancia az adataik sértetlenségére. Ennek megfelelően a démonszálak alkalmasak bizonyos háztartási feladatokra. Például a Java-nak van egy szála, amely a metódushívások feldolgozásáért felel
finalize()
, azaz a szemétgyűjtőhöz (gc) kapcsolódó szálakért.
Minden szál egy csoport ( ThreadGroup ) része . A csoportok pedig részei lehetnek más csoportoknak, egy bizonyos hierarchiát vagy struktúrát alkotva.
public static void main(String[] args) {
Thread currentThread = Thread.currentThread();
ThreadGroup threadGroup = currentThread.getThreadGroup();
System.out.println("Thread: " + currentThread.getName());
System.out.println("Thread Group: " + threadGroup.getName());
System.out.println("Parent Group: " + threadGroup.getParent().getName());
}
A csoportok rendet teremtenek a szálkezelésben. A csoportokon kívül a szálak saját kivételkezelővel is rendelkeznek. Vessen egy pillantást egy példára:
public static void main(String[] args) {
Thread th = Thread.currentThread();
th.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("An error occurred: " + e.getMessage());
}
});
System.out.println(2/0);
}
A nullával való osztás hibát okoz, amelyet a kezelő elkap. Ha nem ad meg saját kezelőt, akkor a JVM meghívja az alapértelmezett kezelőt, amely a kivétel veremnyomkövetését adja ki az StdError-nak. Minden szálnak van prioritása is. A prioritásokról ebben a cikkben olvashat bővebben:
Java Thread Priority in Multithreading .
Egy szál létrehozása
Ahogy a dokumentációban is szerepel, kétféleképpen hozhatunk létre szálat. Az első módszer a saját alosztály létrehozása. Például:
public class HelloWorld{
public static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello, World!");
}
}
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
}
}
Mint látható, a feladat munkája a metódusban történik
run()
, de maga a szál elindul a metódusban
start()
. Ne keverje össze ezeket a módszereket: ha közvetlenül hívjuk az r
un()
metódust, akkor nem indul új szál. Ez az a
start()
módszer, amely arra kéri a JVM-et, hogy hozzon létre egy új szálat. Ez az opció, ahol a szálat örököljük, már rossz abból a szempontból, hogy az osztályhierarchiánkban szerepel a Thread. A második hátrány, hogy kezdjük megsérteni az „egyedülálló felelősség” elvét. Vagyis a mi osztályunk egyidejűleg felelős a szál vezérléséért és az ebben a szálban végrehajtandó feladatokért. Mi a helyes út? A válasz ugyanabban a módszerben található
run()
, amelyet felülírunk:
public void run() {
if (target != null) {
target.run();
}
}
Íme
target
néhány
java.lang.Runnable
, amelyet átadhatunk a Thread osztály példányának létrehozásakor. Ez azt jelenti, hogy ezt tehetjük:
public class HelloWorld{
public static void main(String[] args) {
Runnable task = new Runnable() {
public void run() {
System.out.println("Hello, World!");
}
};
Thread thread = new Thread(task);
thread.start();
}
}
Runnable
a Java 1.8 óta funkcionális felület is. Ez lehetővé teszi, hogy még szebb kódot írjunk egy szál feladatához:
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Hello, World!");
};
Thread thread = new Thread(task);
thread.start();
}
Következtetés
Remélem, ez a vita tisztázza, mi az a szál, hogyan jönnek létre a szálak, és milyen alapvető műveleteket lehet végrehajtani a szálakkal. A
következő részben megpróbáljuk megérteni, hogyan hatnak egymásra a szálak, és megvizsgáljuk a szálak életciklusát.
Jobb együtt: Java és a Thread osztály. II. rész – Szinkronizálás Jobb együtt: Java és a Thread osztály. III. rész – Interakció Jobb együtt: Java és a szál osztály. IV. rész – Hívható, jövő és barátok Jobb együtt: Java és a szál osztály. V. rész – Végrehajtó, ThreadPool, Fork/Join Better together: Java és a Thread osztály. VI. rész – Tüzet el!
GO TO FULL VERSION