Arvutiteaduse instituut
  1. Kursused
  2. 2022/23 kevad
  3. Objektorienteeritud programmeerimine (LTAT.03.003)
EN
Logi sisse

Objektorienteeritud programmeerimine 2022/23 kevad

  • Kodutööd ja praktikumid
  • Loengud
  • Kursuse korraldus
  • IDE juhendid
  • Silumisest
  • Edasijõudnute rühm

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.

  • Arvutiteaduse instituut
  • Loodus- ja täppisteaduste valdkond
  • Tartu Ülikool
Tehniliste probleemide või küsimuste korral kirjuta:

Kursuse sisu ja korralduslike küsimustega pöörduge kursuse korraldajate poole.
Õppematerjalide varalised autoriõigused kuuluvad Tartu Ülikoolile. Õppematerjalide kasutamine on lubatud autoriõiguse seaduses ettenähtud teose vaba kasutamise eesmärkidel ja tingimustel. Õppematerjalide kasutamisel on kasutaja kohustatud viitama õppematerjalide autorile.
Õppematerjalide kasutamine muudel eesmärkidel on lubatud ainult Tartu Ülikooli eelneval kirjalikul nõusolekul.
Courses’i keskkonna kasutustingimused