Gyakorlati alapok III.

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

 

A volatile kulcsszó

 

Az előző, A synchronized kulcsszó című fejezetben megoldást találtunk arra a speciális esetre, amely során 2 vagy több szál ugyanazon erőforrást kívánja használni, még pedig úgy, hogy a végeredmény elvárt módon ne legyen káosz (inkonzisztencia), holtpont vagy egyéb hibaüzenet. Ezt a problémát a synchronized kulcsszó bevezetésével, az erőforráshoz való hozzáférések szinkronizációjával sikerült megoldanunk. A kulcsszó nem használható osztályok, interfészek, valamint változók esetében.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

A többszörös hozzáférés azonban nemcsak metódusokra, hanem változókra is igen veszélyes.

 

Ez a probléma olyan változók esetében fordul elő, amelyek egy megosztott objektumban, osztályszinten vannak deklarálva...

 

public class megosztottObjektum {
 

    public int szamlalo = 0;

 

}

 

...és amelyet ezért egyszerre több, külön processzormagon futó szál is képes használni (amely használat alatt elsősorban írási és olvasási jogot értek). A probléma azonban nem az eléréssel, hanem a deklarált változó pillanatnyi értékeivel van, amelyek ilyenkor nem mindig láthatók minden szál számára. Ez viszont előre nem látható eredményeket produkálhat. A helyzet demonstrálására tekintsük meg az alábbi ábrát:

 

Forrás - Source: tutorials.jenkov.com

 

A változó nemcsak a főmemóriában (main memory) foglal helyet, hanem róla lokális másolatok is keletkeznek a gyorsító memóriában (cache memory), még pedig mindegyik dedikált szálon, amely változó értékeit a szálak átállíthatják anélkül, hogy az láthatóvá válna a "globális" főmemóriában.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

A változó értékeinek folyamatos láthatóságát minden jogosult szál számára Java 5 verziótól kezdve a volatile (= ingadozó) kulcsszó bevezetése garantálja.

 

Az ilyen feltételekkel deklarált változót tehát reflexszerűen így érdemes deklarálnunk:

 

public class megosztottObjektum {
 

    public volatile int szamlalo = 0;

 

}

 

Nézzük is meg gyorsan az ilyen módon módosított, futtatható Java-kódunkat, amelyben 2 szál egy közös változó értékeit növeli. Elvárt eredményünk a következő volna:

 

A valtozo tartalma: 1

A valtozo tartalma: 2

A valtozo tartalma: 3

A valtozo tartalma: 4

A valtozo tartalma: 5

A valtozo tartalma: 6

A valtozo tartalma: 7

A valtozo tartalma: 8

A valtozo tartalma: 9

A valtozo tartalma: 10

 

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

 

 

 

 

 

 

 


class Szal1 extends Thread {
    munkaOsztaly mo;
    Szal1(munkaOsztaly mo){
    this.mo = mo;
    this.setName("Szal1");
}

public void run(){
    mo.metodus(this.getName());
    }
}

class Szal2 extends Thread {
    munkaOsztaly mo;
    Szal2(munkaOsztaly mo){
    this.mo = mo;
    this.setName("Szal2");
}

public void run() {
    mo.metodus(this.getName());
    }
}

class munkaOsztaly{

public volatile int szamlalo = 1;

void metodus(String nev) {
    for(int i = 1; i <= 5; i++) {
        try{
            Thread.sleep(1000);
        }catch(Exception e){System.out.println(e);
    }
    System.out.println(nev + " - A valtozo tartalma: " + szamlalo);
    szamlalo++;
        }
    }
}

class Main {
public static void main(String args[]) {
    munkaOsztaly mo = new munkaOsztaly();
    Szal1 szal1 = new Szal1(mo);
    Szal2 szal2 = new Szal2(mo);
    szal1.start();
    szal2.start();
    }
}

 

Végeredmény:
Szal1 - A valtozo tartalma: 1
Szal2 - A valtozo tartalma: 1
Szal2 - A valtozo tartalma: 3
Szal1 - A valtozo tartalma: 3
Szal2 - A valtozo tartalma: 5
Szal1 - A valtozo tartalma: 5
Szal1 - A valtozo tartalma: 7
Szal2 - A valtozo tartalma: 7
Szal1 - A valtozo tartalma: 9
Szal2 - A valtozo tartalma: 9

 

Az elvárt eredmények sajnos nem megfelelők. Vajon mi a hiba?

 

Hiba nincs, csak értelmezési zavar, hiszen a volatile kulcsszó kizárólag a változó tartalmának láthatóságára ad megoldást, nem pedig metódusok szinkronizációjára, arra, amelyről éppen az előző, A synchronized kulcsszó című fejezetben beszéltünk. Ha szükséges, metódusokat ezután is szinkronizálnunk kell. Ebből következően a metódus synchronized kulcsszóval való felvértezésével (synchronized void metodus(String nev)), meg fogjuk kapni elvárt eredményeinket:

 

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

 

 

 

 

 

 

 


class Szal1 extends Thread {
    munkaOsztaly mo;
    Szal1(munkaOsztaly mo){
    this.mo = mo;
    this.setName("Szal1");
}

public void run(){
    mo.metodus(this.getName());
    }
}

class Szal2 extends Thread {
    munkaOsztaly mo;
    Szal2(munkaOsztaly mo){
    this.mo = mo;
    this.setName("Szal2");
}

public void run() {
    mo.metodus(this.getName());
    }
}

class munkaOsztaly{

public volatile int szamlalo = 1;

synchronized void metodus(String nev) {
    for(int i = 1; i <= 5; i++) {
        try{
            Thread.sleep(1000);
        }catch(Exception e){System.out.println(e);
    }
    System.out.println(nev + " - A valtozo tartalma: " + szamlalo);
    szamlalo++;
        }
    }
}

class Main {
public static void main(String args[]) {
    munkaOsztaly mo = new munkaOsztaly();
    Szal1 szal1 = new Szal1(mo);
    Szal2 szal2 = new Szal2(mo);
    szal1.start();
    szal2.start();
    }
}

 

Végeredmény:
Szal1 - A valtozo tartalma: 1
Szal1 - A valtozo tartalma: 2
Szal1 - A valtozo tartalma: 3
Szal1 - A valtozo tartalma: 4
Szal1 - A valtozo tartalma: 5
Szal2 - A valtozo tartalma: 6
Szal2 - A valtozo tartalma: 7
Szal2 - A valtozo tartalma: 8
Szal2 - A valtozo tartalma: 9
Szal2 - A valtozo tartalma: 10

 

A fenti példákban 2 szál együttesen használt fel egy számlálót. A Java-rendszerben azonban -pontosan ilyen esetekre-, elérhető egy sokkal biztonságosabb megoldás is az AtomicInteger nevű osztályban, amely a...

 

java.util.concurrent.atomic.AtomicInteger

 

...elérési úton található. Az alábbi futtatható Java-kód ilyen típusú objektummal lett módosítva a munkaOsztaly osztályban:

 

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

 

 

 

 

 

 

 


java.util.concurrent.atomic.AtomicInteger

 

class Szal1 extends Thread {
    munkaOsztaly mo;
    Szal1(munkaOsztaly mo){
    this.mo = mo;
    this.setName("Szal1");
}

public void run(){
    mo.metodus(this.getName());
    }
}

class Szal2 extends Thread {
    munkaOsztaly mo;
    Szal2(munkaOsztaly mo){
    this.mo = mo;
    this.setName("Szal2");
}

public void run() {
    mo.metodus(this.getName());
    }
}

class munkaOsztaly{

AtomicInteger
atomicInteger = new AtomicInteger(1);

synchronized void metodus(String nev) {
    for(int i = 1; i <= 5; i++) {
        try{
            Thread.sleep(1000);
        }catch(Exception e){System.out.println(e);
    }
    System.out.println(nev + " - A valtozo tartalma: " + atomicInteger);
    atomicInteger.getAndIncrement();
        }
    }
}

class Main {
public static void main(String args[]) {
    munkaOsztaly mo = new munkaOsztaly();
    Szal1 szal1 = new Szal1(mo);
    Szal2 szal2 = new Szal2(mo);
    szal1.start();
    szal2.start();
    }
}

 

Végeredmény:
Szal1 - A valtozo tartalma: 1
Szal1 - A valtozo tartalma: 2
Szal1 - A valtozo tartalma: 3
Szal1 - A valtozo tartalma: 4
Szal1 - A valtozo tartalma: 5
Szal2 - A valtozo tartalma: 6
Szal2 - A valtozo tartalma: 7
Szal2 - A valtozo tartalma: 8
Szal2 - A valtozo tartalma: 9
Szal2 - A valtozo tartalma: 10

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Utolsó gondolatként jegyezzük meg, hogy a volatile kulcsszó nem használható osztályok, interfészek, valamint metódusok esetében.