2010-07-30 10 views
6

W szczególności, czy ktoś może mi powiedzieć, co jest nie tak z tym kodem. Powinien uruchomić wątki, więc powinien wydrukować "Wprowadzanie wątku .." 5 razy, a następnie poczekać na wywołanie funkcji notifyAll(). Ale losowo drukuje "Wprowadzanie ..." i "Gotowe ..." i wciąż czeka na innych.Jak używać protokołu oczekiwania i powiadamiania z wieloma wątkami

public class ThreadTest implements Runnable { 
    private int num; 
    private static Object obj = new Object(); 
    ThreadTest(int n) { 
     num=n; 
    } 
    @Override 
    public void run() { 
     synchronized (obj) { 
      try { 
       System.out.println("Entering thread "+num); 
       obj.wait(); 
       System.out.println("Done Thread "+num); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

    public static void main(String[] args) { 
     Runnable tc; 
     Thread t; 
     for(int i=0;i<5;i++) { 
      tc = new ThreadTest(i); 
      t = new Thread(tc); 
      t.start(); 
     } 
     synchronized (obj) { 
      obj.notifyAll(); 
     } 
    } 
} 

Odpowiedz

8

nie robisz nic złego z rażąco wywołań metod, ale masz race condition.

Chociaż w idealnym świecie główny wątek osiągnie swój zsynchronizowany blok po tym, jak wszystkie wątki robocze osiągną wywołanie wait(), , nie ma gwarancji, że (wyraźnie powiedziałeś maszynie wirtualnej, której nie chcesz wątki do wykonania sekwencyjnie z głównym wątkiem poprzez wykonanie wątków). Może się zdarzyć (na przykład, jeśli masz tylko jeden rdzeń), że program planujący wątek decyduje się na natychmiastowe zablokowanie wszystkich wątków roboczych, dlatego zezwala na kontynuowanie głównego wątku. Możliwe, że wątki robocze są wyłączone z kontekstu z powodu braku pamięci podręcznej. Możliwe, że jeden wątek roboczy blokuje wejścia/wyjścia (instrukcja drukowania), a główny wątek jest w tym miejscu przełączany.

W ten sposób, jeśli główny wątek udaje się dotrzeć do zsynchronizowanego bloku, zanim wszystkie wątki robocze osiągną wywołanie wait(), te wątki robocze, które nie osiągnęły wywołania wait(), nie będą działały zgodnie z zamierzeniami. Ponieważ obecna konfiguracja nie pozwala na kontrolowanie tego, musisz dodać wyraźną obsługę tego. Możesz dodać zmienną, która jest inkrementowana, ponieważ każdy wątek roboczy osiąga wait() i ma główny wątek nie wywołuje funkcji notifyAll(), dopóki ta zmienna nie osiągnie 5 lub możesz mieć główną pętlę wątków i wielokrotnie wywoływać notifyAll(), aby wątki robocze były publikowane w wielu grupach.

Zapoznaj się z pakietem java.util.concurrent - istnieje kilka klas blokady, które zapewniają więcej możliwości niż podstawowe synchronizowane blokady - jak zawsze, Java nie pozwala na ponowne wynalezienie koła. CountDownLatch wydają się być szczególnie istotne.

Podsumowując, współbieżność to trudna. Musisz zaprojektować, aby upewnić się, że wszystko nadal działa, gdy wątki są wykonywane w zamówieniach, których nie chcesz, a także w zamówieniach, które chcesz.

+0

Dzięki Scott za odpowiedź. Myślę, że prostym rozwiązaniem byłoby umieszczenie Thread.sleep (1000) tuż przed notifyAll(), aby upewnić się, że wszystkie wątki przeszły w stan wait(). – figaro

+0

To może dobrze działać, ale jest to trochę naiwne rozwiązanie. Jeśli chcesz wykonać więcej pracy przed wywołaniem oczekiwania, jeśli konieczne będzie, aby wątki robocze zostały zwolnione tak szybko, jak to możliwe, lub nawet jeśli wątki robocze potrwają dłużej niż oceniłeś, wystąpi ten sam problem. * Musisz * używać blokowania. Właśnie o to chodzi w programowaniu współbieżnym. Spójrz na kod Davida Blevina: jest to doskonały przykład wykorzystania blokad odliczania. – Scott

0

Problem główny z kodem jest, że niektóre z wątków nie dostać się do waitpo nazywa główny wątek notifyAll. Tak więc, gdy czekają, nic ich nie obudzi.

Aby to działało (przy użyciu oczekiwania/powiadomień), musisz zsynchronizować główny wątek, tak aby czekał na wszystkie wątki potomne, aby uzyskać stan, w którym mogą otrzymać powiadomienie przed wykonaniem tego połączenia.

Ogólnie rzecz biorąc, synchronizacja z wait, notify i prymitywnymi blokadami jest trudna. W większości przypadków uzyskasz lepsze wyniki (tj. Prostszy, bardziej niezawodny i bardziej wydajny kod) przy użyciu klas narzędzi współbieżności Java.

2

I sekundę zalecenia CountDownLatch. Oto wzór używam na moich testach wielowątkowych:

final int threadCount = 200; 

final CountDownLatch startPistol = new CountDownLatch(1); 
final CountDownLatch startingLine = new CountDownLatch(threadCount); 
final CountDownLatch finishingLine = new CountDownLatch(threadCount); 

// Do a business method... 
Runnable r = new Runnable() { 
    public void run() { 
     startingLine.countDown(); 
     try { 
      startPistol.await(); 

      // TODO: challenge the multithreadedness here 

     } catch (InterruptedException e) { 
      Thread.interrupted(); 
     } 
     finishingLine.countDown(); 
    } 
}; 

// -- READY -- 

for (int i = 0; i < threadCount; i++) { 
    Thread t = new Thread(r); 
    t.start(); 
} 

// Wait for the beans to reach the finish line 
startingLine.await(1000, TimeUnit.MILLISECONDS); 

// -- SET -- 

// TODO Assert no one has started yet 

// -- GO -- 

startPistol.countDown(); // go 

assertTrue(finishingLine.await(5000, TimeUnit.MILLISECONDS)); 

// -- DONE -- 

// TODO: final assert 

Chodzi o to, aby zagwarantować bardzo następnej linii kodu twoje wątki wykona to taka, która kwestionuje multithreadedness lub tak blisko niego jak to możliwe. Myślę o wątkach jako biegaczy na torze.Dostajesz je na torze (tworzenie/uruchamianie), czekaj, aż staną się idealnie na linii startowej (wszyscy nazwali startLine.countDown()), następnie wystrzeliwują pistolet startowy (startPistol.countDown()) i czekają na wszystkich do przekroczenia linii mety (finishLine.countDown()).

[EDYCJA] Należy zauważyć, że jeśli nie masz żadnego kodu lub kontroli, które chcesz wykonać między startLine.await() i startingPistol.countDown(), możesz połączyć zarówno startLine, jak i initialPistol w jedną CyclicBarrier (threadCount) + 1). Podwójne podejście CountDownLatch jest w zasadzie takie samo i pozwala głównym wątkom testowym wykonać dowolną konfigurację/sprawdzanie po wyrównaniu wszystkich innych wątków i zanim zaczną one działać, jeśli będzie to potrzebne.

Powiązane problemy