Article View: pl.comp.lang.java
Article #154017Re: Wielowatkowość i Singleton
From: Arivald
Date: Fri, 30 May 2014 18:51
Date: Fri, 30 May 2014 18:51
158 lines
5548 bytes
5548 bytes
On 2014-05-30 19:21, tomjas wrote:
> Witam.
>
> Tak w ramach ćwiczeń postanowiłem sobie poćwiczyć różne wersje wzorca
> singleton. Poniżej kod testujący singleton w najprostszej wersji
> Lazy-Initialization (oczywiście abstrahujemy od jakości wersji wzorca
> - wiem, że nie jest to najlepsze rozwiązanie). A więc sam signleton:
>
> #v+
> class Counter {
> private static Counter INSTANCE;
>
> private Counter(){}
>
> public static synchronized Counter getInstance() {
> if (INSTANCE == null) {
> INSTANCE = new Counter();
> }
> return INSTANCE;
> }
> }
To nie jest najwydajniejsza implementacja singletona z lazy initem.
Najlepsza w Javie jest oparta na wewnętrznej klasie.
public class Something {
private Something() {}
private static class LazyHolder {
private static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
Ten pattern gwarantuje leniwą inicjalizację, i nie ma straty wydajnosći
na synchronizowaniu.
>
> teraz klasa, która zawiera kod w tworzonych wątkach. Do konstruktora
> przekazuję hashmapę, w której będę zliczał obiekty o takim samym
> hashkodzie:
>
>
> #v+
> class CounterThread implements Runnable {
>
> Map<Integer, Integer> map;
>
> public CounterThread(Map<Integer, Integer> map) {
> this.map = map;
> }
>
> @Override
> public void run() {
> Counter cnt = Counter.getInstance();
> int hash = cnt.hashCode();
> Integer freq = map.get(hash);
> map.put(hash, (freq == null) ? 1 : freq + 1);
Te dwie linie to popularny błąd wiary w cudowne właściwości
Collections.synchronizedMap().
Pomiędzy map.get(hash) a map.put(hash, ..) inny wątek może zmienić mapę,
i dane lecą w kosmos (czyli któryś z wątków zapisze do mapy wartość
która już tam jest, choć wydaje mu się że ją zwiększył).
Żeby było poprawnie i bezpiecznie to te dwie linie należy
zsynchronizować na mapie, np tak:
class CounterThread implements Runnable {
Map<Integer, Integer> map;
public CounterThread(Map<Integer, Integer> map) {
this.map = map;
}
@Override
public void run() {
Counter cnt = Counter.getInstance();
int hash = cnt.hashCode();
synchronized(map) {
Integer freq = map.get(hash);
map.put(hash, (freq == null) ? 1 : freq + 1);
}
}
}
... i w takim wypadku mapa nie musi być już przepiszczana poprzez
Collections.synchronizedMap().
Albo użyj synchronizowanej mapy, w wersji która dla get nieznanego
klucza automatycznie tworzy parę klucz/wartość, a jako wartość użyj
AtomicInteger.
Kod wywołania wyglądał by wtedy tak:
public void run() {
Counter cnt = Counter.getInstance();
int hash = cnt.hashCode();
map.get(hash).incrementAndGet();
}
> }
> }
> #v-
>
> No i klasa tworząca 1000 wątków.
>
> #v+
> public class Main {
> public static void main(String[] args) {
>
> Map<Integer, Integer> map = Collections
> .synchronizedMap(new HashMap<Integer, Integer>());
>
> for (int i = 0; i < 1_000; i++) {
> new Thread(new CounterThread(map)).start();
> }
>
> try {
> Thread.sleep(5000);
> System.out.println(map);
> } catch (InterruptedException e) {
> }
> }
> }
> #v-
>
>
> Dla pewności daję głównegmu wątkowi odpocząć 5 sek i sprawdzam hashmapę.
> A wynik wygląda np. tak:
>
> {1704732977=897}
>
>
> Rzecz jasna za każdym wywołaniem zmienia się wartość hashkodu i ich
> liczba, ale - i tu moje pytanie - co z resztą wątków tworzących obiekty
> singletona (Counter) i licząca dla nich haskody? Przecież powinno być
> ich 1000. Gdzie popełniam błąd, bo już mi monitor oczy wypala
> od wpatrywania?
>
Z powodu błędów i wyścigu wątków nie każdy wątek zdołał zwiększyć licznik.
> Dodatkowo, dlaczego jeśli utworzę zwykłą hashmapę bez synchronizacji, to
> jej odczyt wygląda jak powyżej, a odczyt jej wielkości (map.size())
> potrafi pokazać 2-5 elementów?
Bo zwykła mapa bez synchronizacji nie jest bezpieczna przy korzystaniu
wielowątkowym, i cuda się dzieją => zachowanie niezdefiniowane.
--
Arivald
Technical Details
Message-ID:
<lmajv8$3sl$1@news.dialog.net.pl>Path:
mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!agh.edu.pl!news.agh.edu.pl!newsfeed2.atman.pl!newsfeed.atman.pl!news.nask.pl!news.nask.org.pl!newsfeed.pionier.net.pl!news.dialog.net.pl!not-for-mailReferences:
<slrnlohfgo.ngq.tomjas@darkstar.example.net>Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!agh.edu.pl!news.agh.edu.pl!newsfeed2.atman.pl!newsfeed.atman.pl!news.nask.pl!news.nask.org.pl!newsfeed.pionier.net.pl!news.dialog.net.pl!not-for-mail From: Arivald <NOSPAMarivald@interia.pl> Newsgroups: pl.comp.lang.java Subject: Re: Wielowatkowość i Singleton Date: Fri, 30 May 2014 20:51:11 +0200 Organization: Dialog Net Lines: 160 Message-ID: <lmajv8$3sl$1@news.dialog.net.pl> References: <slrnlohfgo.ngq.tomjas@darkstar.example.net> NNTP-Posting-Host: 78.10.239.172 Mime-Version: 1.0 X-Trace: news.dialog.net.pl 1401475880 3989 78.10.239.172 (30 May 2014 18:51:20 GMT) X-Complaints-To: abuse@dialog.net.pl NNTP-Posting-Date: Fri, 30 May 2014 18:51:20 +0000 (UTC) User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Thunderbird/30.0 In-Reply-To: <slrnlohfgo.ngq.tomjas@darkstar.example.net> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit