2011-10-20 10 views
6

Zastanawiam o wyniku niebezpiecznej zmniejszanie/zwiększając w wątkach Javie, więc jest mój program:Wątek niebezpieczny dekompresowanie/inkrementowanie - dlaczego w większości pozytywne?

główne klasy:

public class Start { 

    public static void main(String[] args) { 

     int count = 10000000, pos = 0, neg = 0, zero = 0; 

     for (int x=0; x<10000; x++) { 

      Magic.counter = 0; 

      Thread dec = new Thread(new Magic(false, count)); 
      Thread inc = new Thread(new Magic(true, count)); 

      dec.start(); 
      inc.start(); 

      try { 
       inc.join(); 
       dec.join(); 
      } catch (InterruptedException e) { 
       System.out.println("Error"); 
      } 

      if (Magic.counter == 0) 
       zero++; 
      else if (Magic.counter > 0) 
       pos++; 
      else 
       neg++; 
     } 

     System.out.println(Integer.toString(neg) + "\t\t\t" + Integer.toString(pos) + "\t\t\t" + Integer.toString(zero)); 
    } 
} 

wątki klasa:

public class Magic implements Runnable { 

    public static int counter = 0; 

    private boolean inc; 
    private int countTo; 

    public Magic(boolean inc, int countTo) { 
     this.inc = inc; 
     this.countTo = countTo; 
    } 

    @Override 
    public void run() { 

     for (int i=0;i<this.countTo;i++) { 

      if (this.inc) 
       Magic.counter++; 
      else 
       Magic.counter--; 
     } 

    } 
} 

Mam biegać program kilka razy i zawsze uzyskując znacznie bardziej pozytywny wynik, a następnie ujemny. Próbowałem również zmienić kolejność wątków, ale nic to nie zmieniło. Niektóre wyniki:

Number of results < 0 | Number of results > 0 | Number of results = 0 

1103    8893    4 
3159    6838    3 
2639    7359    2 
3240    6755    5 
3264    6728    8 
2883    7112    5 
2973    7021    6 
3123    6873    4 
2882    7113    5 
3098    6896    6 
+0

Możesz chcieć uczynić Magic.counter zmienna lotny, poza operacje mogą zostać zmieniona i/lub jego wartość może być buforowane per-thread w rejestrze. – prunge

Odpowiedz

6

Założę widać dokładnie odwrotne zachowanie z następującymi zmianami (czyli odwrócić oddziały bez zmieniania czegokolwiek innego):

if (this.inc) 
    Magic.counter--; // note change, and lie about `this.inc` 
else 
    Magic.counter++; 

Jeśli prawdziwej, co mogłoby to wskazuje na to, że wskazują na interakcje wątku?

Teraz, dla zabawy, zmień lot na zmienny - jak zmieniane są wyniki?

Co powiesz na usunięcie volatile i otworzenie if/else za pomocą lock? (A lock zapewnia pełne zabezpieczenie pamięci i ustanawia krytyczny obszar, zawsze powinien dawać doskonałe wyniki.)

Szczęśliwe kodowanie.


warte rozważenia:

  1. Kod wygląda tylko na mniejszy lub większy do zera, a nie ogólny dryf/odmiana: +1 lub -1 jest potrzebny, aby przechylić szalę. (Bardziej przydatne może być poszerzenie zebranych danych).
  2. Wykonanie gałęzi "jeszcze" zajmuje nieco więcej czasu, ponieważ wymagany jest skok; zwykle jest to problem, ale ponad 10 milionów cykli ... jeden lub dwa to niewiele.
  3. Brak liści lotnych/ogrodzeń pamięciowych dla dużo lee-way w widoczności zmiennej Magic.counter. (Wierzę, że zgodna maszyna JVM może faktycznie przynieść o wiele gorsze wyniki ...)
  4. Operatory ++ i -- są z natury nieatomowe.
  5. Przeplatanie wątków jest ogólnie "niedeterministyczne"; mniej, jeśli jest wykonywany w wielu rdzeniach.
+0

Zmiana kolejności Magic.counter -/++ "pomaga" trochę, ale nie jest dokładnie odwrotnie :) jej jak 5,5k negatyw, 4,5k pozytywny – Nazin

+0

@Nazin Ciekawe, dzięki za zgłoszenie :) Jeden z Problem polega na tym, że ++ i - są nieatomowe, a wątki przeplatają się w taki sposób, że odczyt/zapis jest "podzielony" (w tym przypadku operacyjność i widoczność wartości między wątkami). Wartość 'volatile' powinna pomóc w widoczności wartości, ale jest niewystarczająca, aby zapewnić krytyczną operację' ++/- '(i tym samym atomową). –

+0

@Nazin Być może ktoś z bardziej skomplikowanymi szczegółami implementacji [konkretnej] JVM może wiedzieć, dlaczego '++' i '--' nie wydają się być w tym aspekcie całkowicie symetryczne. Najlepsze, co mogę zaoferować na tym poziomie, to "niedeterministyczne" ;-) –

0

Zasadniczo wynika to ze sposobu działania modelu pamięci Java. Uzyskujesz dostęp do wspólnych zmiennych w dwóch różnych wątkach bez synchronizacji. Ani zmienna nie jest zadeklarowana jako zmienna, ani nie wykonujesz operacji atomowych. Brak koordynacji i zmiennych atomowych lub zmiennych lotnych spowoduje wewnętrzne optymalizacje kodu gwintowego wykonanego przez maszynę JVM podczas wykonywania. Ponadto zmienne nielotne, które nie przekroczyły bariery pamięci (tj. synchronized), doprowadzą do buforowanych wartości na wątek - a zatem do dwóch konfliktowych kopii lokalnych wątków wewnątrz ich pamięci podręcznych.

Z uwagi na brak sekwencyjnego modelu spójności w Javie, złożone optymalizacje środowiska wykonawczego oraz specyfikę używanej maszyny JVM i systemu bazowego (pojedynczego lub wielordzeniowego, hyperthreading), niemożliwe jest przewidywanie wyniku w sposób deterministyczny - tylko dlatego, że narusza kilka konwencji wielowątkowości dla modelu języka Java. Uruchomienie tego samego kodu na tym samym komputerze może nadal prowadzić do podobnych wyników, ale ze względu na efekty harmonogramowania wątków, użycie procesora w innych procesach systemu operacyjnego itp., Prawdopodobnie nie będą one dokładnie takie same.

Oto niektóre zasoby o JMM: http://www.cs.umd.edu/~pugh/java/memoryModel/