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
.