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 i 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”.