Institute of Computer Science
  1. Courses
  2. 2024/25 spring
  3. Object-oriented Programming (LTAT.03.003)
ET
Log in

Object-oriented Programming 2024/25 spring

  • Kodutööd ja praktikumid
  • Loengud
  • Kursuse korraldus
  • IDE juhend
  • Süvendusrühm
  • Silumisest

Lõimede ülesannete lahendused

Ülesanne 4

public class KaitstudMassiiv {

  private int[] andmed = new int[100];

  public synchronized void set(int positsioon, int väärtus) {
    andmed[positsioon] = väärtus;
  }

  public synchronized int suurus() {
    return andmed.length;
  }

  public synchronized int[] kõikVäärtused() {
    return andmed;
  }
}

public class Test {
  public static void main(String[] args) {
    KaitstudMassiiv kaitstud = new KaitstudMassiiv();
    for (int i = 0; i < kaitstud.suurus(); i++) {
      kaitstud.set(i, i * 10);
    }
    int[] tulemus = kaitstud.kõikVäärtused();
    for (int element : tulemus) {
      System.out.println(element);
    }
  }
}

Selle ülesande probleem peitub kõikVäärtused meetodis. Kirjutame selle pikalt välja:

public int[] kõikVäärtused() {
  synchronized (this) {
    return andmed;
  }
}

Sünkronisatsiooni eesmärk on tagada, et mitu lõime ei saaks korraga samu andmeid kasutada. Meetod kõikVäärtused loeb massiivi viida kriitilises sektsioonis, mis tagab, et ükski teine KaitstudMassiiv meetod ei muuda tol hetkel seda massiivi. Seejärel meetod tagastab viida ja väljub kriitilisest sektsioonist. Häda on selles, et nüüd on main meetodil otseviit andmed massiivile. Kui main hakkab massiivi sisu for-tsükliga läbima, siis ta teeb seda väljaspool ühtegi kriitilist sektsiooni. Kuigi KaitstudMassiiv meetodid omavahel ei saa jätkuvalt samaaegselt massiivi kasutada, võib main nendega samaaegselt massiivi kasutada, sest ta ei tea sünkronisatsioonist midagi.

Kuidas teha paremini?

Lahendus 1

Kõik meetodid, mis andmed massiivi kasutavad, peaks olema sama monitori peal sünkroniseeritud. Kuna main kasutab massiivi, saaks seal massiivi kasutava koodijupi kriitiliseks sektsiooniks märkida:

public static void main(String[] args) {
  KaitstudMassiiv kaitstud = new KaitstudMassiiv();
  for (int i = 0; i < kaitstud.suurus(); i++) {
    kaitstud.set(i, i * 10);
  }
  int[] tulemus = kaitstud.kõikVäärtused();
  synchronized (kaitstud) {
    for (int element : tulemus) {
      System.out.println(element);
    }
  }
}

Sellist koodi ei ole lihtne lugeda, sest sünkroniseerimine on mitme klassi peal laiali. Lisaks peab main meetod teadma liiga palju KaitstudMassiiv siseelust - mis andmed on sünkroniseeritud, mis objekte kasutatakse monitorina jne. Lahendus toimib, aga on suhteliselt ebameeldiv. Pigem kasutada teisi lahendusi.

Lahendus 2

Tegelikult ei ole main meetodil vaja massiivi ennast vaid selle sees olevaid väärtusi. Hea lahendus oleks massiivi mitte kunagi KaitstudMassiiv objektist välja näidata. Niimoodi on kindel, et kõik kriitilised sektsioonid on alati ainult ühes klassis, mitte igal pool laiali. Muudame KaitstudMassiiv klassi nii, et massiivi tagastamise asemel saab seal sisalduvaid väärtusi vaadata:

public class KaitstudMassiiv {

  public synchronized void set(int positsioon, int väärtus) { ... }

  public synchronized int suurus() { ... }

  public synchronized int get(int positsioon) {
    return andmed[positsioon];
  }
}

Lahendus 3

Kõige lihtsam variant on andmed massiivi tagastamise asemel tagastada massiivist koopia. Niimoodi saab meetodi kutsuja teha koopiaga, mida iganes ta tahab ja sünkroniseerimine ei muutu kuidagi raskemaks:

public synchronized int[] kõikVäärtused() {
  return Arrays.copyOf(andmed);
}

Ülesanne 5

public class KaitstudHashMap {

  private Map<String, String> andmed = new HashMap<>();

  public void lisa(String võti, String väärtus) {
    synchronized (this) {
      if (!andmed.containsKey(võti)) {
        andmed.put(võti, väärtus);
      }
    }
  }

  public String asenda(String võti, String väärtus) {
    synchronized (this) {
      if (andmed.containsKey(võti)) {
        String vanaVäärtus = andmed.get(võti);
        andmed.put(võti, väärtus);
        return vanaVäärtus;
      }
      return null;
    }
  }

  public String otsi(String võti, String kuiVäärtusPuudub) {
    synchronized (andmed) {
      if (andmed.containsKey(võti)) {
        return andmed.get(võti);
      }
      return kuiVäärtusPuudub;
    }
  }
}

Selles ülesandes on probleem otsi meetodis. Mingite andmete sünkroniseerimiseks peaks kõik koodijupid, mis neid andmeid kasutavad, olema kriitiliseks sektsiooniks märgitud. Lisaks peavad nad kasutama sama monitori. Antud juhul otsi meetod kasutab teistest KaitstudHashMap meetoditest erinevat monitori, kuigi kõik meetodid töötavad samade andmete peal.

Kui üks lõim on lisa meetodis, siis samal ajal ei saa teine lõim lisa ega asenda meetodit kasutada, sest monitor lubab enda kriitilistesse sektsioonidesse ainult ühe lõime korraga. Küll aga saab üks lõim kasutada lisa või asenda meetodit ja teine lõim kasutada samal ajal otsi meetodit, sest meetodid kasutavad eri monitore. Selle tulemusena saavad kaks lõime korraga andmed väljale ligi, mis võib põhjustada võidujooksu.

Lahendus

Kasutada sama välja sünkroniseerimiseks alati sama monitori. Meetodis otsi peaks kasutama monitorina this või kõigis teistes meetodites kasutama monitorina andmed.

  • Institute of Computer Science
  • Faculty of Science and Technology
  • University of Tartu
In case of technical problems or questions write to:

Contact the course organizers with the organizational and course content questions.
The proprietary copyrights of educational materials belong to the University of Tartu. The use of educational materials is permitted for the purposes and under the conditions provided for in the copyright law for the free use of a work. When using educational materials, the user is obligated to give credit to the author of the educational materials.
The use of educational materials for other purposes is allowed only with the prior written consent of the University of Tartu.
Terms of use for the Courses environment