🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: pl.comp.lang.java
Article #154017

Re: Wielowatkowość i Singleton

#154017
From: Arivald
Date: Fri, 30 May 2014 18:51
158 lines
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-mail
References: <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