Definiowanie wątków

Definiowanie wątków poprzez implementację podklas klasy java.lang.Thread – tak jak to zrobiliśmy w artykule „Programowanie współbieżne” – jest najprostszą możliwą metodą, jednak nie zawsze najwygodniejszą. Alternatywnie, wątek możemy zdefiniować implementując interfejs java.lang.Runnable i na podstawie takiej klasy tworząc instancje klasy Thread, które to instancje są koniec końców tak czy inaczej potrzebne – aby wątek uruchomić.

Ekwiwalentem klasy MyThread której kod znajdziemy w artykule „Programowanie współbieżne”, która to klasa implementuje wątek rozszerzając klasę Thread jest klasa implementująca interfejs Runnable:

class MyRunnable implements Runnable {
  public void run() {
    System.out.println("Witaj w świecie programowania współbieżnego!");
  }
}

Aby tak zdefiniowany wątek uruchomić potrzebujemy utworzyć instancję klasy MyRunnable i następnie na podstawie tej instancji instancję klasy Thread. Wątek, niezależnie od sposobu implementacji uruchamiamy w ten sam sposób, tj. wywołując metodę start(). Aby uruchomić wątek zdefiniowany w postaci klasy MyRunnable moglibyśmy napisać:

public class TestClass {
  public static void main(String[] args) {
    Runnable runnable = new MyRunnable();
    Thread newThread = new Thread(runnable);

    newThread.start();
  }
}

Pójdźmy teraz o krok dalej, i uruchommy kilka wątków. Każda instancja klasy Thread lub jej podklasy odpowiada jednemu wątkowi, żeby więc uruchomić kilka wątków będziemy musieli utworzyć kilka instancji. Chcąc uruchomić kilka wątków zdefiniowanych w postaci klasy MyThread moglibyśmy napisać:

public class TestClass {
  public static void main(String[] args) {
    Thread threadA = new MyThread();
    Thread threadB = new MyThread();
    Thread threadC = new MyThread();

    threadA.start();
    threadB.start();
    threadC.start();
  }
}

Aby uruchomić trzy wątki zdefiniowane w postaci klasy MyRunnable możemy postąpić dwojako. Możemy napisać tak:

public class TestClassA {
  public static void main(String[] args) {
    Runnable runnable = new MyRunnable();
  
    Thread threadA = new Thread(runnable);
    Thread threadB = new Thread(runnable);
    Thread threadC = new Thread(runnable);
  
    threadA.start();
    threadB.start();
    threadC.start();
  }
}

albo tak:

public class TestClassB {
  public static void main(String[] args) {
    Thread threadA = new Thread(new MyRunnable());
    Thread threadB = new Thread(new MyRunnable());
    Thread threadC = new Thread(new MyRunnable());
  
    threadA.start();
    threadB.start();
    threadC.start();
  }
}

Wbrew pozorom, dwie pokazane powyżej klasy, tj. klasy TestClassA TestClassB, różnią się od siebie diametralnie. TestClassB to odpowiednik klasy TestClass z poprzedniego przykładu. Zarówno kod klasy TestClassB jak i klasy TestClass tworzy trzy instancje klasy definiującej kod wykonywany przez wątek i uruchamia trzy wątki, po jednym dla każdej instancji. Kod klasy TestClassA dla odmiany tworzy tylko jedną instancję klasy definiującej  kod wykonywany przez wszystkie trzy wątki. Co prawda tworzymy w klasie TestClassA trzy instancje klasy Thread, jednak to nie klasa Thread definiuje kod wykonywany przez wątki, tylko klasa MyRunnable, której instancję mamy jedną – wszystkie trzy instancje klasy Thread zostały utworzone na bazie tej samej instancji klasy MyRunnable.

To, czy kilka wątków wykonuje współbieżnie ten sam kod, czy każdy z nich swoją kopię takiego samego kodu ma zasadnicze znaczenie. Przekonajmy się o tym na przykładzie. Zmieńmy implementację klasy MyRunnable na pokazaną poniżej:

class MyRunnable implements Runnable {
  int val = 0;
  
  public void run() {
    val = val + 1;
  
    System.out.println(val);
  }
}

Uruchommy teraz klasę testującą TestClassA. Wynikiem będzie zapewne ciąg liczb 1, 2, 3 (choć niekoniecznie tak być musi, ale o tym w następnych artykułach). Uruchommy też klasę TestClassB. Wynikiem zawsze będzie ciąg liczb 1, 1, 1 (tak, trzy razy liczba 1). Dlaczego tak się dzieje? To wyjaśniam w następnym odcinku kursu, odcinku „Wykonanie wielowątkowe”.