Gyakorlati alapok III.

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

 

A synchronized kulcsszó

 

Több program, illetve programszál egyidejű futtatásakor felmerülő állandó probléma, hogy erőforrásokon kell megosztozniuk. Sőt, lehetnek dedikáltan közös munkaterületek, erőforrások is. Rögtön tegyük is hozzá, ez egy operációs rendszeren belül tipikusnak mondható. Nem nehéz kitalálnunk a negatív következményeit annak, ha a metódusoknak megengedjük, hogy spontán, szabályozatlanul használhassák fel erőforrásaikat: ebben az esetben a "közös lónak túrós háta" elv fog következetesen érvényülni, amely minimum kaotikus kimeneti adatokhoz, szoftveres "ütközésekhez", vagy súlyosabb helyzetben holtponthoz (deadlock) fog vezetni. Az alábbi képen a lehető legtipikusabb holtpontot tanulmányozhatjuk:

 

www.informatika-programozas.hu - Holtpont

 

Holtpont kialakulásához az vezetett, hogy mindegyik autós úgy gondolta, neki van előnye és nem volt olyan szabályozó mechanizmus, amely az autósok szándékait összehangolta, szinkronizálta volna.

 

A fenti példa ténylegesen vagy más, de hasonló variációban előfordulhat belső metódusok és osztályok, tágabb vonatkozásban önállóan futó programok esetében is. A Java 5 verziótól kezdve van lehetőségünk ezen problémát hatékonyan lekezelni.

 

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

 

A Java-rendszer ehhez egy beépített biztonsági objektumot biztosít, ezen objektum neve lock vagy monitor, helye a java.util.concurrent.locks csomagban található. Minden létrehozott objektum rendelkezik egy ilyen hozzárendelt szabályzóval. Ha egy szál hozzáférést kér az objektumhoz, ezt nem teheti meg direkt foglalással, hanem először a lock-monitor szabályzóhoz kell fordulnia, amely a foglalást vagy engedélyezi vagy várakoztatja (blokkolja). Tehát voltaképpen -a fenti közlekedési példát továbbgondolva-, egyfajta közlekedési lámpaként forgalomirányítást végez.

 

A probléma illusztrálására nézzünk meg egy jellemző példát!

 

A létrehozott 2 szál (Szal1 és Szal2) folyamatosan használ fel egy MunkaOsztaly nevű objektumot saját neveik többszöri kinyomtatására. A végeredmény bár hibamentes, ám a kiírási sorrend mégis kaotikus, mert a szálak szabályozatlanul férnek hozzá a MunkaOsztaly nevű objektumhoz, mint közös (itt: nyomtató) erőforráshoz:

 

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.nevKiiras(this.getName());
    }
}

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

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

class MunkaOsztaly{
    void nevKiiras(String nev) {
    for(int i = 1; i <= 5; i++) {
        System.out.println(nev);
        try{
            Thread.sleep(500);
            }catch(Exception e){System.out.println(e);
            }
        }
    }
}

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
Szal2
Szal1
Szal2
Szal1
Szal2
Szal2
Szal1
Szal2
Szal1


A végeredmény azonban többszöri futtatással mindig más lesz:

 

Végeredmény:
Szal1
Szal2
Szal1
Szal2
Szal1
Szal2
Szal1
Szal2
Szal2
Szal1

 

Végeredmény:
Szal1
Szal2
Szal1
Szal2
Szal2
Szal1
Szal1
Szal2
Szal1
Szal2
 

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

 

A megoldás, hogy synchronized kulcsszó használatával aktiváljuk a lock-monitor szabályzót, hogy egyszerre csak 1 szál férjen az erőforráshoz.

 

A kulcsszót mindig azon objektum elé kell tennünk, amelyet ilyen módon védeni kívánunk, ebben az esetben a MunkaOsztaly nevKiiras() metódusa elé (synchronized void nevKiiras(String nev)):

 

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.nevKiiras(this.getName());
    }
}

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

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

class MunkaOsztaly{
    synchronized
void nevKiiras(String nev) {
    for(int i = 1; i <= 5; i++) {
        System.out.println(nev);
        try{
            Thread.sleep(500);
            }catch(Exception e){System.out.println(e);
            }
        }
    }
}

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
Szal1
Szal1
Szal1
Szal1
Szal2
Szal2
Szal2
Szal2
Szal2

 

Az elvárt eredmény nem marad el, hiszen teljes szabályozottságban először a Szal1 fér hozzá a nyomtató metódushoz, és csak utána Szal2.

 

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

 

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