Pro jistotu jsem se dival do zdrojaku Collections.java, viz nize.
Z toho uz je to snad zrejme, ze vsechny cteni a zapisy jsou vuci sobe
synchronizovane pres stejny objekt (tu mapu).
public static Map synchronizedMap(Map m) {
return new SynchronizedMap(m);
}
private static class SynchronizedMap implements Map, Serializable {
private Map m; // Backing Map
Object mutex; // Object on which to synchronize
SynchronizedMap(Map m) {
this.m = m; mutex = this;
}
SynchronizedMap(Map m, Object mutex) {
this.m = m; this.mutex = mutex;
}
public int size() {
synchronized(mutex) {return m.size();}
}
public boolean isEmpty(){
synchronized(mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized(mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value){
synchronized(mutex) {return m.containsValue(value);}
}
public Object get(Object key) {
synchronized(mutex) {return m.get(key);}
}
public Object put(Object key, Object value) {
synchronized(mutex) {return m.put(key, value);}
}
public Object remove(Object key) {
synchronized(mutex) {return m.remove(key);}
}
public void putAll(Map map) {
synchronized(mutex) {m.putAll(map);}
}
public void clear() {
synchronized(mutex) {m.clear();}
}
private transient Set keySet = null;
private transient Set entrySet = null;
private transient Collection values = null;
public Set keySet() {
synchronized(mutex) {
if (keySet==null)
keySet = new SynchronizedSet(m.keySet(), this);
return keySet;
}
}
public Set entrySet() {
synchronized(mutex) {
if (entrySet==null)
entrySet = new SynchronizedSet(m.entrySet(), this);
return entrySet;
}
}
public Collection values() {
synchronized(mutex) {
if (values==null)
values = new SynchronizedCollection(m.values(), this);
return values;
}
}
public boolean equals(Object o) {
synchronized(mutex) {return m.equals(o);}
}
public int hashCode() {
synchronized(mutex) {return m.hashCode();}
}
}
Lukas Barton napsal(a):
Jiri Dolezel napsal(a):
iii) to Lukas B.:
Tak to je prave to, cim si nejsem jisty. Mas pravdu, ze set/get jsou vzajemne
synchronizovane, coz zaruci vzajemne blokovani cteni a zapisu. Dokonce mame i
zaruceno, ze je kazdy Produkt vytvoren prave jednou. Ale je zaruceno to, ze se
nekonzistentni produkt nedostane do jineho vlakna? Viz uvedeny priklad, kdy
vlakno A ulozi nekonzistentni objekt do mapy a pak je preruseno. Podle me to
zaruceno neni, protoze to by musely byt soucasne definovany prvky mapy jako
volatile.
Proc? Vzdyt zapis a cteni jsou synchronizovani pres tu hashMapu.
To zapisujici vlakno tam vzdy ulozi konzistentni stav, protoze ten
zapis do synchronizedMap je taky synchronizovany, coz zaruci, ze
vsechny instrukce proveden pred zapisem se provedou pred jakymkoliv
ctenim z te mapy. Prectete si poradne ve specifikaci o Java Memory
Modelu, tam je to popsane.
Vazne tam zadny problem neni.
Lukas
Jinak diky za zajimavy napad s dalsim urychlenim. V mem pripade to vsak nema
vyznam, protoze produkty se incializuji jednou a pak se uz se jen ctou - a ta
trocha blokovani na zacatku nas nezabije :-)
Diky vsem,
Jirka
Ahoj,
uvedeny kod je spravne i pro viceprocesorove stroje.
Situace popsana v 1 nastat nemuze protoze kazdy zapis a cteni z
cacheOfProducts je synchronizovane (synchronizedMap) - tj. cteni zde po
zapisu vzdy pres synchronized. (put happens before get)
A pri vytvareni nove hodnoty synchronizuji vsechny vlanka pres
cacheOfProcuts, takze kazdy produkt je vytvoren prave jednou.
Pokud byste ten kod chtel jeste vice zrychlit (pokud soucasne prijde
vice vlaken s pozadavkem na ruzny produkt budou na sebe cekat na
cacheOfProducts), muzete zacit synchronizovat na nejakem wrapperu
Stringu vytvorenem stejnym zpusobem jako produkt (to ma smysl pouze
pokud new Produkt(name) muze byt hodne pomale - napr. cteni z DB). ;-)
Lukas
Jiri Dolezel wrote:
Ahoj,
pracuji s JEE aplikaci provozovanou na viceprocesorovem stroji (Java 5), kde se
casto pouzivaji konstrukce tohoto typu:
**** zacatek prikladu ****
class ProductsCache {
// staticka cache: "nazev produktu" -> "DTO produktu"
private static final Map<String,Product> cacheOfProducts
= Collections.synchronizedMap(new HashMap<String,Product>());
public Product get(String name) {
// nejdrive jej zkusime najit v cache
Product product = cacheOfProducts.get(name);
if (product == null) {
// pokud v cache neni, vytvor jej v synchronizovanem bloku
synchronized (cacheOfProducts) {
// "double-checked-locking"?
product = cacheOfProducts.get(name);
if (product == null) {
// PROBLEM: Muze se nam stat, ze se neuplne inicializovany produkt ulozi do cache
// a jine vlakno tak ziska nekonzistentni produkt?
product = new Product(name);
cacheOfProducts.put(name, product);
}
}
}
return product;
}
}
**** konec prikladu ****
Mam k tomuto kodu dve otazky:
1) Muze skutecne dojit k situaci popsane v kodu jako "PROBLEM"? Podle me ano,
pokud prekladac/CPU prohodi instrukce takto:
- (vlakno A) vytvor novy objekt Produkt "nazev"
- (vlakno A) proved cacheOfProducts.put("nazev", product);
- (vlakno B) zavolej cacheOfProducts.get("nazev") => ziska nekonzistentni
produkt
- (vlakno A) dokonci inicializaci produktu "nazev"
2) Pokud skutecne muze nastat situace 1), nabizi Java 5 moznost, jak tomu predejit, aniz bychom synchronizovali celou metodu get()?
Zkoumal jsem pouziti volatile za timto ucelem (tzn. deklarovat cacheOfProducts jako volatile), ale to se vylucuje s final.
Ikdyby vsak cacheOfProducts nebylo final, resilo by volatile tento problem? Ve specifikaci se uvadi, ze prvky pole, ktere je deklarovane jako volatile, samy volatile nejsou. A jelikoz kolekce je postavena na polich, tak si myslim, ze to problem opet neresi. Ale to jsou jen me dohady.
Prosim, pokud do toho vidite, vysvetlete mi cely problem jednou provzdy :-)
Jirka