Blog
Skip to end of metadata
Go to start of metadata

Java programozók körében elterjedt tévhit, hogy a synchronized módosítóval ellátott metódusok vagy blokkok által bezárt programsorokat csak egy szál futtathatja egy időben. A valóság az, hogy a kérdéses osztály példányaira vonatkozik a synchronized, ha az osztályból van kettő példányunk, amelyek egy közös static változót használnának, akkor mit sem ér a synchronized... Hogy ne a levegőbe beszéljek, nézzünk egy konkrét példát, vegyünk például egy osztályt, amelynek egy statikus változója és két metódusa van:

Test.java
public class Test
{
  private static Integer counter=0;
 
  public synchronized void addToCounter(Integer number)
  {
    counter += number;
  }
 
  public Integer getCounter()
  {
    return counter;
  }
}

Láthatjuk, hogy az addToCounter metódus szinkronizált blokkban van, így azt a counter nevű változóhoz több szál nem férhet hozzá:

ThreadA.java
public class ThreadA implements Runnable
{
  @Override
  public void run()
  {
    try
    {
      Test test = new Test();
      for (int count = 0; count < 100; count++)
      {
        test.addToCounter(1);
        Thread.sleep(7);
      }
      System.out.println(test.getCounter());
    } catch (InterruptedException ex)
    {
      Logger.getLogger(ThreadA.class.getName()).log(Level.SEVERE, ex.toString(), ex);
    }
  }
}

Ahogy fentebb látszik, létrehoztunk egy ThreadA (és egy ThreadB) osztályt, amelyekkel több szálon tudjuk futtatni a Test osztály "szálbiztos" addToCounter metódusát:

Main.java
public class Main
{
  public Main()
  {
    ThreadA a=new ThreadA();
    ThreadB b=new ThreadB();
   
    new Thread(a).start();
    new Thread(b).start();
  }
 
  public static void main(String[] args)
  {
    new Main();
  }
}

Az eremény meglepő lehet:

197
198

Elvileg az eremény 200 kellene lennie az utolsónak lefutó szál esetén, hiszen minden hozzáadás szinkronizált volt... azonban a synchronized két külön példányra volt értelmezett, így a statikus változót két szál akár egyidőben is módosíthatta. A fent vázolt probléma abból a tényból adódik, hogy a synchronized módosítóval jelölt metódus (lásd fentebb) az alábbi kóddal lesz azonos:

public void addToCounter(Integer number)
{
  synchronized (this)
  {
    counter += number;
  }
}

Ahogy a kódból is kiolvasható: a kód szálzárása példány szintű lesz (this), s ebből adódóan csak akkor jön elő a probléma, ha statikus változót akarunk egy nem statikus - de szinkronizált metódusból elérni. A megoldás egyszerű, kell készítenünk egy statikus és szinkronizált metódust, vagy osztály szintű szálzárást állítunk be:

public void addToCounter(Integer number)
{
  synchronized (getClass())
  {
    counter += number;
  }
}

Érdemes figyelni ilyen apróságokra, hiszen egy többszálú programban hibát keresni nem egyszerű, s egy tévhit még nehezebbé teheti a hiba okának felderítését.

      
      
Page viewed times

4 Comments

  1. Ha jól sejtem elírás a ThreadB, és helyesen

    ThreadA a=new ThreadA();
    ThreadA b=new ThreadA();
    
    
    1. Auth Gábor AUTHOR

      Nem, direkt van két szálimplementáció (nehogy azt mondja valaki, hogy azért téveszt a számláló mert azonos osztály példányaival számolok), de akár így is működhetne, a probléma tekintetében mindegy... (smile)

  2. Akkor aztért az valahol le kellene írni, hogy a ThreadB copy-paste ugyanaz, mint a ThreadA. Amúgy didaktikailag azt tartanám helyesnek, ha ThreadA lenne mind a két objektum, és meg lehet említeni, ha valakinek nem lenne egyértelmű, hogy ha a két osztály nem azonos, csak mind a kettő párhuzamosan matat, akkor is ez van.

    Esetleg érdemes lenne ezt a cikket frissíteni és hozzátenni az atomi osztályokról és CAS-ról is egy megoldást. Mert amúgy a példafeladathoz a mai Java-val már nem kell synchronized.

    1. Auth Gábor AUTHOR

      Ahogy fentebb látszik, létrehoztunk egy ThreadA (és egy ThreadB) osztályt

      Leírtam... (smile)

      Esetleg érdemes lenne ezt a cikket frissíteni és hozzátenni az atomi osztályokról és CAS-ról is egy megoldást. Mert amúgy a példafeladathoz a mai Java-val már nem kell synchronized.

      Bárkinek nyitott a lehetőség, hogy tartalmat adjon hozzá... (smile)

#trackbackRdf ($trackbackUtils.getContentIdentifier($page) $page.title $trackbackUtils.getPingUrl($page))