Gyakorlati alapok III.

A többszálú programozás alapjai (multithread)

 

Néhány alapvető szálmetódus

 

.start()

Amint az előző fejezetben említésre került és láthattuk is, a szálat ez a metódus indítja. Természetesen lehetőségünk van arra, hogy először létrehozzuk a szálat, majd később futtassuk...

 

Szal1 szal1 = new Szal1("Szal 1");
szal1.start();

 

...a fenti parancsokkal:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


public class Szal1 extends Thread {

public Szal1(String nev) {
    super(nev);
}

public void run() {
    for(int i = 1; i <= 10; i++) {
        System.out.println(i + ". ciklus - " + getName());
    }
    System.out.println(getName() + " kesz!");
    }
}
public class Main {
public static void main (String[] args) {
    Szal1 szal1 = new Szal1("Szal 1");
    szal1.start();
    }
}

 

Végeredmény:
1. ciklus - Szal 1
2. ciklus - Szal 1
3. ciklus - Szal 1
4. ciklus - Szal 1
5. ciklus - Szal 1
6. ciklus - Szal 1
7. ciklus - Szal 1
8. ciklus - Szal 1
9. ciklus - Szal 1
10. ciklus - Szal 1
Szal 1 kesz!


.stop()
A metódus minden további kérdezés nélkül leállítja a szálat. Éppen ebben rejlik használatának veszélye, hiszen fennáll a szál működésének félbeszakítása, amely adatvesztést és egyéb zavarokat okozhat. Érdemes tehát nagyon körültekintően használni, vagy hagyni a szál természetes elhalását, amely a run() metódusban írt kódok problémamentes lefutását jelenti.


.getState()
Ez a metódus tudósít minket a szál pillanatnyi futási állapotáról. Ezek a következők lehetnek:

NEW - RUNNABLE

 

A metódus alapstátuszainak elsődleges vizsgálatához illesszük azt a program fő pontjaira, azaz irassuk ki a szál pillanatnyi futási állapotait. A fő pontok:

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


public class Szal1 extends Thread {

public Szal1(String nev) {
    super(nev);
}

public void run() {
    System.out.println("run() eleje: " + getState());
        for(int i = 1; i <= 10; i++) {
            System.out.println(i + ". ciklus - " + getName());
        }
    System.out.println(getName() + " kesz!");
    System.out.println("run() vege: " + getState());
    }
}

public class Main {
public static void main (String[] args) {
    Szal1 szal1 = new Szal1("Szal 1");
    System.out.println("main() - peldanyositas utan, start() elott: " + szal1.getState());
    szal1.start();
    System.out.println("main() - start() utan: " + szal1.getState());
    }
}
 

Végeredmény:
main() - peldanyositas utan, start() elott: NEW
main() - start() utan: RUNNABLE
run() eleje: RUNNABLE
1. ciklus - Szal 1
2. ciklus - Szal 1
3. ciklus - Szal 1
4. ciklus - Szal 1
5. ciklus - Szal 1
6. ciklus - Szal 1
7. ciklus - Szal 1
8. ciklus - Szal 1
9. ciklus - Szal 1
10. ciklus - Szal 1
Szal 1 kesz!
run() vége: RUNNABLE


Mivel a helyzet nem bonyolult és a szál gond nélkül lefutott, első körben 2 állapotot tudtunk tettenérni, a NEW és RUNNABLE státuszokat.

 

BLOCKED - TIMED_WAITING - TERMINATED

 

Ezen állapotok tettenérése kissé bonyolultabb ügy. Először a TERMINATED  vonatkozásában gondoljunk abba bele, hogy vajon egy elhalt szál ki tudja-e jelenteni magáról, hogy "én meg vagyok szüntetve (TERMINATED)"? Természetesen nem, elvégre a halott szál egészen az, ezért a szálfutás körülményeinek menedzseléséhez egy olyan másik osztályt is találnunk kell, amely hatékonyan képes a folyamatokat felügyelni. Ez az osztály a külön fejezetben ismertetett Runtime, amely fejezetet érdemes a Java-tanulónak eme fejezet továbbolvasása előtt felkeresnie.

 

www.informatika-programozas.hu - További információk!

A Runtime osztálynak van egy különleges metódusa, neve: addShutdownHook(Thread hook). Ez a metódus lehetőséget ad arra, hogy külön szálként tetszőleges kód fusson le a JVM közvetlen leállítása előtt. Ez például arra lehet jó, ha a JVM rutinszerű leállása mellett szükségünk van további, egyedi rutinokat tartalmazó leállítási műveletekre. Az alábbiakban csak szálstátusz meghatározására fogjuk felhasználni.

 

A TERMINATED státusz invokációjához tehát az előbbieknél jelentős mértékben kell futtatható Java-kódunkat átszabni:

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


public class Main {

public static class Szal extends Thread {
    @Override
    public void run() {
        szalAllapot("Szal elindult", this);
        for(int i = 1; i <= 5; i++) {
            System.out.println(i + ". ciklus - " + getName());
            }
        System.out.println(getName() + " kesz!");
    szalAllapot("Szal befejezi", this);
    }

static void szalAllapot(String uzenet, Thread szal) {
    System.out.printf("%s - %s allapot: %s%n", uzenet, szal.getName(), szal.getState());
}

public static void main(String[] args) {
    Szal szal = new Szal();
    szal.setName("Szal");
    szalAllapot("Szal start() elott", szal);
    szal.start();
    szalAllapot("Szal start() utan", szal);
    szalAllapot("main() alszik 1 masodpercig", szal);
    try {
        Thread.sleep(1000);
        } catch (Exception e) {
                e.printStackTrace();
        }
    szalAllapot("main() felebred", szal);

    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        szalAllapot("Leallas", szal);
            }));
        }
    }
}

 

Végeredmény:

Szal start() elott - Szal allapot: NEW
Szal start() utan - Szal allapot: RUNNABLE
main() alszik 1 masodpercig - Szal allapot: RUNNABLE
Szal elindult - Szal allapot: RUNNABLE
1. ciklus - Szal
2. ciklus - Szal
3. ciklus - Szal
4. ciklus - Szal
5. ciklus - Szal
Szal kesz!
Szal befejezi - Szal allapot: RUNNABLE
main() felebred - Szal allapot: TERMINATED
Leallas - Szal allapot: TERMINATED

 

Másodsorban pedig a BLOCKED és a TIMED_WAITING státuszokkal kell foglalkoznunk, amely a szál blokkolását, valamint várakozását jelenti egy időzített metódus hívására. A torlódásos helyzetet, vele ezen státuszok felröppenését a várakozások megfelelő ütköztetésével tudjuk a rendszerből kiprovokálni. A kódmódosítás a fentiek szerinti, de a Szal folyamatból kivesszük a ciklikus kiírást, helyette kap egy kötelező, 3 másodperces elmerengést a Thread.sleep(3000) metódus hívásával.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


public class Main {

private static class Szal extends Thread {
    @Override
    public void run() {
    szalAllapot("Szal alszik 3 masodpercig", this);
    try {
        Thread.sleep(3000);
    } catch (Exception e) {
            e.printStackTrace();
        System.out.println(getName() + " kesz!");
    szalAllapot("Szal befejezi", this);
    }
}

static void szalAllapot(String uzenet, Thread szal) {
    System.out.printf("%s - %s allapot: %s%n", uzenet, szal.getName(), szal.getState());
}

public static void main(String[] args) {
    Szal szal = new Szal();
    szal.setName("Szal");
    szalAllapot("Szal start() elott", szal);
    szal.start();
    szalAllapot("Szal start() utan", szal);
    szalAllapot("main() alszik 1 masodpercig", szal);
    try {
        Thread.sleep(1000);
        } catch (Exception e) {
                e.printStackTrace();
        }
    szalAllapot("main() felebred", szal);

    Runtime.getRuntime().addShutdownHook(new Thread(() -> {
        szalAllapot("Leallas", szal);
            }));
        }
    }
}

 

Végeredmény:

Szal start() elott - Szal allapot: NEW
Szal start() utan - Szal allapot: RUNNABLE
main() alszik 1 masodpercig - Szal allapot: BLOCKED
Szal alszik 3 masodpercig - Szal allapot: RUNNABLE
main() felebred - Szal allapot: TIMED_WAITING
Leallas - Szal allapot: TERMINATED

 

Itt jegyezzük meg, hogy a BLOCKED státusz nem mindig kerül kiírásra, azaz a szálfutás a fenti kódban nem mindig blokkolódik. Ez a JVM pillanatnyi hangulatától, azaz attól függ, hogy a szálakat aktuálisan miképpen csoportosítja, illetve futtatja le.


.isAlive()
Ez a metódus boolean típusú függvényként (TRUE – FALSE) igaz értéket ad vissza, ha a szál el lett indítva, de még nem lett leállítva, azonban hamis, ha a szál státusza NEW vagy TERMINATED. Alapszintű teszteléséhez használjuk fel a függvényt a getState() függvény közelségében:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


public class Szal1 extends Thread {

public Szal1(String nev) {
    super(nev);
}

public void run() {
    System.out.println("run() eleje - " + getState());
    System.out.println("run() eleje - is alive? " + isAlive());
    for(int i = 1; i <= 10; i++) {
        System.out.println(i + ". ciklus - " + getName());
    }
    System.out.println(getName() + " kesz!");
    System.out.println("run() vege - " + getState());
    System.out.println("run() vege - is alive? " + isAlive());
    }
}


public class Main {
public static void main (String[] args) {
    Szal1 szal1 = new Szal1("Szal 1");
    System.out.println("main() - peldanyositas utan, start() elott - " + szal1.getState());
    System.out.println("main() - peldanyositas utan, start() elott - is alive? " + szal1.isAlive());
    szal1.start();
    System.out.println("main() - start() utan - is alive? " + szal1.isAlive());
    }
}

 

Végeredmény:
main() - peldanyositas utan, start() elott - NEW
main() - peldanyositas utan, start() elott - is alive? false
main() - start() utan - is alive? true
run() eleje - RUNNABLE
run() eleje - is alive? true
1. ciklus - Szal 1
2. ciklus - Szal 1
3. ciklus - Szal 1
4. ciklus - Szal 1
5. ciklus - Szal 1
6. ciklus - Szal 1
7. ciklus - Szal 1
8. ciklus - Szal 1
9. ciklus - Szal 1
10. ciklus - Szal 1
Szal 1 kesz!


.sleep()
Már említett módon a szál futása megszakítható vagy külső behatásra megszakadhat. Egy szál akkor kerül nem futtatott-nem futtatható állapotba:

Nézzük meg a sleep() egyszerű használatát a run() függvényen belül. A metódus Csipkerózsika-szerűen elaltatja a szálat 5 másodperc hosszan (sleep(5000)). Vegyük észre, hogy az altatást kötelező try-catch blokkba kell tennünk:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


public class Szal1 extends Thread {

public Szal1(String nev) {
    super(nev);
}

public void run() {
    System.out.println("run() eleje - " + getState());
    System.out.println("run() eleje - is alive? " + isAlive());
    for(int i = 1; i <= 10; i++) {
        System.out.println(i + ". ciklus - " + getName());
    }
    try {
        System.out.println("Szál 1 várakozik... - "+ getState() + "... Is alive? " + isAlive());
        sleep(5000);
        System.out.println(getName() + " kesz!");
        System.out.println("run() vege - " + getState());
        System.out.println("run() vege - is alive? " + isAlive());
        } catch (InterruptedException e){
    }
    }
}


public class Main {
public static void main (String[] args) {
    Szal1 szal1 = new Szal1("Szal 1");
    System.out.println("main() - peldanyositas utan, start() elott - " + szal1.getState());
    System.out.println("main() - peldanyositas utan, start() elott - is alive? " + szal1.isAlive());
    szal1.start();
    System.out.println("main() - start() utan - is alive? " + szal1.isAlive());
    }
}

 

Végeredmény:
main() - peldanyositas utan, start() elott - NEW
main() - peldanyositas utan, start() elott - is alive? false
main() - start() utan - is alive? true
run() eleje - RUNNABLE
run() eleje - is alive? true
1. ciklus - Szal 1
2. ciklus - Szal 1
3. ciklus - Szal 1
4. ciklus - Szal 1
5. ciklus - Szal 1
6. ciklus - Szal 1
7. ciklus - Szal 1
8. ciklus - Szal 1
9. ciklus - Szal 1
10. ciklus - Szal 1
Szal 1 várakozik... - RUNNABLE... Is alive? true
Szal 1 kesz!
run() vege - RUNNABLE
run() vege - is alive? True


set.Priority()
Ezen függvénnyel a szálak között azok futtatásakor prioritási (elsődlegességi) sorrend állítható be. Az érték 1 és 10 között lehetséges, ahol 10 a leginkább elsődleges, 1 pedig a legkevésbé az. A prioritási értékeket a get.Priority() függvénnyel tudjuk lekérdezni. Alapértelmezésben minden szál az 5 értéket kapja, amely prioritási szempontból nem súlyozott.


Az alábbi futtatható Java-kódban a Szal2 szálat állítottuk elsődlegesnek (10) és azt vizsgáljuk, hogy valóban előbb fog-e végezni Szal1-nél (1). A különbségek azonban még nem igazán jönnek ki alacsony szálterhelések mellett, ezért a szálak for ciklusait 1000-re állítottam:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


public class Szal1 extends Thread {

public Szal1(String nev) {
    super(nev);
}

public void run() {
    for(int i = 1; i <= 1000; i++) {
        System.out.println(i + ". ciklus - " + getName());
    }
    System.out.println(getName() + " kesz!");
    }
}


public class Szal2 extends Thread {

public Szal2(String nev) {
    super(nev);
}

public void run() {
    for(int i = 1; i <= 1000; i++) {
        System.out.println(i + ". ciklus - " + getName());
    }
    System.out.println(getName() + " kesz!");
    }
}


public class Main {
public static void main (String[] args) {
    Szal1 szal1 = new Szal1("Szal 1");
    Szal2 szal2 = new Szal2("Szal 2");
    szal2.setPriority(10);
    szal1.setPriority(1);
    szal1.start();
    szal2.start();
    }
}

 

Végeredmény:

993. ciklus - Szal 2
994. ciklus - Szal 2
995. ciklus - Szal 2
996. ciklus - Szal 2
997. ciklus - Szal 2
998. ciklus - Szal 2
999. ciklus - Szal 2
1000. ciklus - Szal 2
Szal 2 kesz!
145. ciklus - Szal 1
146. ciklus - Szal 1
147. ciklus - Szal 1
148. ciklus - Szal 1
149. ciklus - Szal 1
150. ciklus - Szal 1
151. ciklus - Szal 1
152. ciklus - Szal 1
153. ciklus - Szal 1


A terhelés hatására Szal2 valóban jóval hamarabb végez, hiszen ekkor Szal1 még csak a 145. ciklusnál tart.