Wybór języka

Testowanie Wątków Wirtualnych

Bądźmy szczerzy: na Wątki Wirtualne (ang. Virtual Threads) czekaliśmy dość długo. Czekaliśmy z niecierpliwością, widzieliśmy zmieniające się nazwy (to nie są fibers ludziska), ale oto wreszcie są: Wątki Wirtualne.

Niektórzy twierdzą, że Project Loom jest największą zmianą w Javie od czasu wersji 8, lambd i strumieni. Inni uważają, że jest to jeszcze większa zmiana. Odkładając dyskusje na bok, zapowiada się na wielką zmianę.

Pamiętam, jak byliśmy podekscytowani (byłem wtedy jeszcze na studiach), że w końcu możemy używać prawdziwych wątków w Javie na Windowsie. Jednak czas leci, branża się zmieniła, sprzęt się zmienił, sposób, w jaki budujemy nasze systemy, również się zmienił. Choć znaleźliśmy sposoby na obejście ograniczeń “jeden wątek systemowy na jeden wątek Javy”, to jednak miało to swoją cenę: zagmatwane stosy wywołań i trudne debugowanie to pierwsze, co przychodzi na myśl. Wątki Wirtualne mają za zadanie utrzymać nas w nuuuuudnym, ale łatwym do zrozumienia modelu wątek-na-żądanie (ang. thread-per-request), jednocześnie odblokowując moc krzemu poniżej.

Nie jestem pewien, czy używanie Wątków Wirtualnych jako “tylko wątków” jest zalecanym podejściem. Współbieżność Strukturalna (ang. structured concurrency) może być lepszym sposobem (tak jak executors były lepsze niż “tylko wątki”), ale czasami istnieje potrzeba użycia po prostu wątku. I w takich sytuacjach Virtual Threads powinny być tak łatwe w użyciu, jak stare dobre wątki systemowe, które znamy od lat. (I które, nawiasem mówiąc, nigdzie się nie znikają!).

Mógłbym ładnie przeformułować rzeczy z JEPa-425 na potrzeby tego wpisu. Zamiast tego myślę, że lepiej jest polecić przeczytanie materiału źródłowego, a następnie… przetestować, czy mamy rację. Ponieważ testy to nie tylko weryfikacja systemu, ale może (a nawet przede wszystkim) weryfikacja czy rozumiemy, jak system działa. A skoro już to zrobiłem, to może macie ochotę zobaczyć, jak można konstruować i obsługiwać Wątki Wirtualne. Również po to, aby odpowiedzieć na pytania, które dostaję na konferencjach: czy ThreadLocal są wspierane, czy mogę ustawić priorytety lub ustawić Wątki Wirtualne jako nie-demony. Wszystko dlatego, że “w gębie każdy mocny, pokaż lepiej kod!”

22class VirtThreadsDiffTest {
23
24    Thread thread = Thread.ofVirtual().unstarted(() -> {
25    });
26
27    @Test
28    void shouldThrowExceptionsForUnsupportedMethods() {
29        assertThrows(UnsupportedOperationException.class, () -> thread.stop());
30        assertThrows(UnsupportedOperationException.class, () -> thread.suspend());
31        assertThrows(UnsupportedOperationException.class, () -> thread.resume());
32        assertThrows(IllegalArgumentException.class, () -> thread.setDaemon(false));
33    }
34
35    @Test
36    void shouldOnlyBeDaemon() {
37        thread.setDaemon(true);
38        assertTrue(thread.isDaemon());
39    }
40
41    @Test
42    void shouldAlwaysHaveNormalPriority() {
43        thread.setPriority(Thread.MAX_PRIORITY);
44        assertEquals(Thread.NORM_PRIORITY, thread.getPriority());
45    }
46
47    @Test
48    void shouldSuportThreadLocalVariablesByDefault() throws InterruptedException {
49        var tl = new ThreadLocal<Integer>();
50        Thread.startVirtualThread(() -> {
51            assertNull(tl.get());
52            tl.set(42);
53            assertEquals(42, tl.get());
54        }).join();
55    }
56
57    @Test
58    void shouldAllowOptOutFromThreadLocal() throws InterruptedException {
59        var tl = new ThreadLocal<Integer>();
60        Thread.ofVirtual().allowSetThreadLocals(false).start(() -> {
61            assertThrows(UnsupportedOperationException.class, () -> {
62                tl.set(17);
63            });
64        }).join();
65    }
66
67    @Test
68    void shouldSupportInheritableThreadLocal() throws InterruptedException {
69        var itl = new InheritableThreadLocal<Integer>();
70        Thread.startVirtualThread(() -> {
71            itl.set(33);
72            Thread.startVirtualThread(() -> {
73                assertEquals(33, itl.get());
74            });
75        }).join();
76    }
77
78    @Test
79    void shouldSupportOptingOutInheritableThreadLocal() throws InterruptedException {
80        var itl = new InheritableThreadLocal<Integer>();
81        Thread.startVirtualThread(() -> {
82            itl.set(33);
83            Thread.ofVirtual().inheritInheritableThreadLocals(false).start(() -> {
84                assertNull(itl.get());
85            });
86        }).join();
87    }
88}
Threads, threads, threads!

Czy podoba mi się podejście ze wzorcem budowniczego? Uwielbiam je ;-)

Jasne, jest to pierwszy podgląd projektu Loom w Javie (jako preview feature). Rzeczy mogą się zmienić. Kiedy zobaczymy go jako standardową funkcję? Nie mam pojęcia, mogę tylko mieć nadzieję, że każdy poświęci swój czas, aby dać mu wystarczająco dużo testów i zainteresowania. Lepiej poczekać dłużej, niż skończyć z być może niezbyt fortunnymi decyzjami. (o tobie mówię, Optional.get ;-)

Kod powyżej pochodzi z mojego projektu DeepDiveJava19, jeśli masz ochotę na inne zmiany w Javie 19.

Czuwaj!

PS. Dodałem obowiązkowe zdjęcie do losowego zdjęcia z nićmi. Najwyraźniej nieposiadanie takiegoż podczas dysputy o projekcie Loom jest przez niektórych postrzegane jako “nieprofesjonalne” ;-)

Wybór języka