Strategia „czekaj-powiadom-powiadom wszystkich” - 1

Witaj Amigo!

Chcę dokładnie omówić z tobą temat oczekiwania-powiadomienia. Metody oczekiwania na powiadomienie zapewniają wygodny mechanizm komunikacji między wątkami. Można ich również używać do budowania złożonych mechanizmów interakcji wątków wysokiego poziomu.

Zacznę od małego przykładu. Załóżmy, że mamy program serwera, który musi wykonywać różne zadania, które użytkownicy dodają za pośrednictwem witryny. Użytkownicy dodają różne oferty pracy w różnych momentach. Zadania wymagają dużej ilości zasobów, ale nasz serwer z ośmiordzeniowym procesorem sobie z nimi poradzi. Jak wykonywać zadania na serwerze?

Najpierw utworzymy grupę wątków roboczych o takiej samej liczbie, jak liczba rdzeni procesora. Każdy wątek będzie mógł pracować na własnym rdzeniu: wątki nie będą ze sobą kolidować, a rdzenie procesora nie będą bezczynne.

Po drugie, stwórzmy obiekt kolejki, w której będą umieszczane zadania otrzymane od użytkowników. Różne typy zadań będą odpowiadać różnym obiektom, ale wszystkie będą implementować interfejs Runnable, aby mogły być wykonywane.

- Czy możesz podać przykład takiego obiektu-zadania?

- Tutaj spójrz:

Klasa oblicza silnię liczby n podczas wywoływania metody run().
class Factorial implements Runnable
{
 public int n = 0;
 public long result = 1;

 public Factorial (int n)
 {
  this.n = n;
 }

 public void run()
 {
  for (int i=2;i<=n;i++)
   result*=i;
 }
}

- Na razie wszystko jasne.

- Świetnie. Następnie przeanalizujemy jak powinien wyglądać obiekt kolejki. Co możesz o nim powiedzieć?

- Musi być bezpieczny dla wątków. Obiekty zadań (zadania) są umieszczane w nim przez wątek, który przyjmuje je od użytkowników, a zadania są podejmowane przez wątki executorów.

- Tak. A jeśli zadania chwilowo się skończyły?

„W takim razie wątki executorów muszą czekać, aż się pojawią.

- Prawidłowy. Następnie wyobraź sobie, że wszystko to można wbudować w jedną kolejkę. Tutaj spójrz:

Kolejka zadań, jeśli nie ma zadania, to wątek zasypia i czeka, aż się pojawi:
public class JobQueue
{
 ArrayList jobs = new ArrayList();

 public synchronized void put(Runnable job)
 {
  jobs.add(job);
  this.notifyAll();
 }

 public synchronized Runnable getJob()
 {
  while (jobs.size()==0)
   this.wait();

  return jobs.remove(0);
 }
}

Mamy metodę getJob , która sprawdza, czy lista zadań (prac) jest pusta, wtedy wątek zasypia (czeka), aż coś pojawi się na liście.

Jest też metoda put , która pozwala dodać nowe zadanie (zadanie) do listy zadań. Zaraz po dodaniu nowego zadania wywoływana jest metoda notifyAll . Wywołanie tej metody obudzi wszystkie wątki robocze, które zasnęły w metodzie getJob.

- Czy możesz mi jeszcze raz przypomnieć, jak działają metody oczekiwania i powiadamiania?

- Metoda wait jest wywoływana tylko wewnątrz zsynchronizowanego bloku, na obiekcie mutex. W naszym przypadku to jest to. W ten sposób dzieją się dwie rzeczy:

1) Wątek zasypia.

2) Wątek tymczasowo zwalnia muteks (dopóki się nie obudzi).

Następnie inne wątki mogą wejść do zsynchronizowanego bloku i zająć ten sam muteks.

Metodę notifyAll można również wywołać tylko wewnątrz zsynchronizowanego bloku obiektu mutex. W naszym przypadku to jest to. W ten sposób dzieją się dwie rzeczy:

1) Wszystkie wątki, które zasnęły na tym samym obiekcie mutex, budzą się.

2) Gdy tylko bieżący wątek opuści zsynchronizowany blok, jeden z obudzonych wątków przejmie muteks i będzie kontynuował swoją pracę. Kiedy zwolni muteks, inny przebudzony wątek przechwyci muteks i tak dalej.

Bardzo podobny do autobusu. Wchodzisz do środka, chcesz przekazać opłatę, ale kierowcy nie ma. I zasypiasz. Z czasem zapełnia Cię cały autobus, ale na razie nikt nie podaje ceny - nie ma nikogo. Potem wchodzi kierowca i słyszysz „- Przekazywanie taryfy”. I tu się zaczyna...

- Ciekawe porównanie. Co to jest autobus?

Julio mi to powiedział. W XXI wieku zdarzały się takie dziwne rzeczy.