Language selector

Testing Virtual Threads

Let’s be honest: we’ve been waiting for the Virtual Threads quite some time. We’ve been waiting eagerly, we’ve seen names changing (they’re not fibers folks), but here there finally are: Virtual Threads.

Some say the Project Loom is the biggest change Java has seen since version 8, lambdas, and streams. Some say it’s even bigger than that. Discussion aside, they are a huge change, it seems.

I remember how we were excited (I was still at the university back then), that we could finally use some real Threads in Java on Windows. Yet time flies by, the industry has changed, the hardware has changed, the way we build our systems has changed too. While we found ways to bypass the limitations of “one OS thread per one Java thread”, it came with some price: convoluted stack traces and difficult debugging are the first ones that come to mind. Virtual Threads are about to keep us in the boooring, yet easy to grasp thread-per-request model, while unblocking the silicon power down below.

I’m not sure if using Virtual Threads as “just threads” is the recommended approach. The structured concurrency might be a better way (just like executors were better than “just threads”), yet sometimes there’s a need to use simply a thread. And in such situations Virtual Threads should be as easy to use as the good ol' Platform Threads we know for years. (And which BTW aren’t going anywhere!)

I could have nicely rephrased stuff from JEP-425 for this post. Instead, I think it’s better to recommend reading the source material and then… test if we get it right. Because tests are not only about verifying the system, but maybe (even predominantly) how we understand how the system works. And since I’ve already done that, perhaps you’d like to see how Virtual Threads can be constructed and handled. Also, to answer questions I get asked at conferences: are ThreadLocals supported, can I set priorities or make VirtualThreads non-daemon. All because “talk is cheap, show me the code!”

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!

Do I like the builder approach? Boy, I love it ;-) Sure, this is the very first preview of the project Loom in Java (as a preview feature). Things might change. When are we going to see it as a standard feature? I can’t tell, I can only hope everyone will take their time to give it enough testing and love. It’s better to wait longer, than end up with some perhaps not-so-fortunate decisions (I’m looking at you, Optional.get ;-)

The code above comes from my DeepDiveJava19 project, should you be interested in running more examples.

Stay tuned!

PS. Mandatory picture of some random threads has been added. Apparently, not having one while talking about project Loom, is seen “unprofessional” by some ;-)

Language selector