Skip to content

Commit ec57111

Browse files
committed
added Java 19 virtual thread examples
1 parent 44f32bf commit ec57111

24 files changed

+328
-3
lines changed

java-19/Fibonacci.java

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import java.util.concurrent.Executor;
2+
import java.util.concurrent.CompletableFuture;
3+
import java.util.concurrent.CompletionStage;
4+
import java.net.URI;
5+
import java.net.http.HttpRequest;
6+
import java.net.http.HttpClient;
7+
import java.time.Duration;
8+
import java.io.IOException;
9+
import java.net.http.HttpClient;
10+
import java.net.http.HttpRequest;
11+
import java.net.http.HttpResponse;
12+
import java.net.http.HttpResponse.BodyHandlers;
13+
14+
/**
15+
* fib of 47 takes ~11s
16+
* fib of 49 fakes ~30s
17+
*/
18+
public class Fibonacci {
19+
public static long fib(int n) {
20+
if (n <= 2)
21+
return 1;
22+
return fib(n - 1) + fib(n - 2);
23+
}
24+
25+
public static CompletableFuture<Long> parallelFib(int n, Executor executor) {
26+
CompletableFuture<Long> partOne = CompletableFuture.supplyAsync(() -> fib(n - 1), executor);
27+
CompletableFuture<Long> partTwo = CompletableFuture.supplyAsync(() -> fib(n - 2), executor);
28+
return partOne.thenCombineAsync(partTwo, (resultOne, resultTwo) -> resultOne + resultTwo, executor);
29+
}
30+
31+
public static CompletableFuture<Long> fibWithRequest(int i, int n, Executor executor) {
32+
return CompletableFuture.supplyAsync(() -> fib(n), executor)
33+
.thenComposeAsync(result -> {
34+
var request = HttpRequest.newBuilder()
35+
.GET()
36+
.uri(URI.create("https://httpbin.org/delay/5"))
37+
.build();
38+
var httpClient = HttpClient.newBuilder()
39+
.version(HttpClient.Version.HTTP_1_1)
40+
.connectTimeout(Duration.ofSeconds(30))
41+
.executor(executor)
42+
.build();
43+
try {
44+
System.out.printf("Thread %d - requesting%n", i);
45+
return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
46+
.thenApplyAsync(resp -> result);
47+
} catch (Exception ex) {
48+
throw new RuntimeException(ex);
49+
}
50+
}, executor);
51+
}
52+
}

java-19/README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Java 18
1+
# Java 19
22

3-
To run each example use: `java --enable-preview --source 18 <FileName.java>`
3+
To run each example use: `java --enable-preview --source 19 <FileName.java>`
44

55
## Features
66

@@ -12,7 +12,8 @@ To run each example use: `java --enable-preview --source 18 <FileName.java>`
1212
* definition: guard is the boolean expression, guarded pattern is the case with guard
1313
* guarded pattern: `case Hero h when h.getCity() == Cities.NEW_YORK`
1414
* guard: `h.getCity() == Cities.NEW_YORK`
15-
* Virtual Threads (user-mode threads)
15+
* Virtual Threads
16+
* also called user-mode threads or [fiber](https://en.wikipedia.org/wiki/Fiber_(computer_science))
1617
* `Virtual threads are lightweight threads that dramatically reduce the effort of writing, maintaining, and observing high-throughput concurrent applications`
1718
* definitions:
1819
* virtual thread is an instance of `java.lang.Thread` that is not tied to a particular OS thread, only consumes an OS thread only while it performs calculations on the CPU;
@@ -28,6 +29,7 @@ To run each example use: `java --enable-preview --source 18 <FileName.java>`
2829
* support thread-local variables and interruption
2930
* more notes about Project Loom [here](../java-loom/)
3031
* we can use `Executors.newVirtualThreadPerTaskExecutor` to create virtual threads
32+
* [here](platform-thread-vs-virtual-thread.md) is some details about the Platform Thread vs Virtual Thread examples
3133

3234
## JEPs
3335

java-19/StressPlatformThread.java

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import java.util.concurrent.Executors;
2+
import java.util.stream.IntStream;
3+
4+
/**
5+
* Run: `java --enable-preview --source 19 <FileName.java>`
6+
*/
7+
public class StressPlatformThread {
8+
public static void main(String[] args) {
9+
var executor = Executors.newCachedThreadPool();
10+
// WARNING: 10K threads sleeping 500 may restart your computer (save everything before run it!)
11+
// IntStream.range(0, 10_000).forEach(i -> {
12+
IntStream.range(0, 1_000).forEach(i -> {
13+
executor.submit(() -> {
14+
System.out.printf("Thread %d - %s%n", i, Thread.currentThread().getName());
15+
try {
16+
Thread.sleep(500);
17+
} catch (InterruptedException e) {
18+
System.out.printf("Thread %d - %s interrupted%n", i, Thread.currentThread().getName());
19+
}
20+
});
21+
});
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import java.util.concurrent.Executors;
2+
import java.util.stream.IntStream;
3+
4+
/**
5+
* Run:
6+
* - `javac Fibonacci.java`
7+
* - `java --enable-preview --source 19 <FileName.java>`
8+
*/
9+
public class StressPlatformThreadWithFibonacci {
10+
public static void main(String[] args) {
11+
var executor = Executors.newCachedThreadPool();
12+
13+
IntStream.range(0, 50).forEach(i -> {
14+
executor.submit(() -> {
15+
Fibonacci.parallelFib(47, executor)
16+
.thenAccept(result -> {
17+
System.out.printf("Platform Thread %d - %d%n", i, result);
18+
});
19+
});
20+
});
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import java.util.concurrent.Executors;
2+
import java.util.stream.IntStream;
3+
4+
/**
5+
* Run:
6+
* - `javac Fibonacci.java`
7+
* - `java --enable-preview --source 19 <FileName.java>`
8+
*/
9+
public class StressPlatformThreadWithFibonacciAndRequest {
10+
public static void main(String[] args) {
11+
var executor = Executors.newCachedThreadPool();
12+
13+
IntStream.range(0, 100).forEach(i -> {
14+
executor.submit(() -> {
15+
Fibonacci.fibWithRequest(i, 40, executor)
16+
.thenAccept(result -> {
17+
System.out.printf("Platform Thread %d - %d%n", i, result);
18+
})
19+
.exceptionally(ex -> {
20+
System.out.printf("Platform Thread %d - error: %s%n", i, ex.getMessage());
21+
return null;
22+
});;
23+
});
24+
});
25+
}
26+
}

java-19/StressVirtualThread.java

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import java.util.concurrent.Executors;
2+
import java.util.stream.IntStream;
3+
4+
/**
5+
* Run: `java --enable-preview --source 19 <FileName.java>`
6+
*/
7+
public class StressVirtualThread {
8+
public static void main(String[] args) {
9+
var executor = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory());
10+
11+
IntStream.range(0, 10_000).forEach(i -> {
12+
executor.submit(() -> {
13+
System.out.printf("VirtualThread %d - %s%n", i, Thread.currentThread().getName());
14+
try {
15+
Thread.sleep(500);
16+
} catch (InterruptedException e) {
17+
System.out.printf("VirtualThread %d - %s interrupted%n", i, Thread.currentThread().getName());
18+
} finally {
19+
System.out.printf("VirtualThread %d - %s woke up%n", i, Thread.currentThread().getName());
20+
}
21+
});
22+
});
23+
24+
while (!executor.isTerminated()) {
25+
;
26+
}
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import java.util.concurrent.Executors;
2+
import java.util.stream.IntStream;
3+
4+
/**
5+
* Run:
6+
* - `javac Fibonacci.java`
7+
* - `java --enable-preview --source 19 <FileName.java>`
8+
*/
9+
public class StressVirtualThreadWithFibonacci {
10+
public static void main(String[] args) {
11+
var executor = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory());
12+
13+
IntStream.range(0, 50).forEach(i -> {
14+
executor.submit(() -> {
15+
Fibonacci.parallelFib(47, executor)
16+
.thenAccept(result -> {
17+
System.out.printf("Virtual Thread %d - %d%n", i, result);
18+
});
19+
});
20+
});
21+
22+
while (!executor.isTerminated()) {
23+
;
24+
}
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import java.util.concurrent.Executors;
2+
import java.util.stream.IntStream;
3+
4+
/**
5+
* Run:
6+
* - `javac Fibonacci.java`
7+
* - `java --enable-preview --source 19 <FileName.java>`
8+
*/
9+
public class StressVirtualThreadWithFibonacciAndRequest {
10+
public static void main(String[] args) {
11+
var executor = Executors.newThreadPerTaskExecutor(Thread.ofVirtual().factory());
12+
13+
IntStream.range(0, 100).forEach(i -> {
14+
executor.submit(() -> {
15+
Fibonacci.fibWithRequest(i, 40, executor)
16+
.thenAccept(result -> {
17+
System.out.printf("Virtual Thread %d - %d%n", i, result);
18+
})
19+
.exceptionally(ex -> {
20+
System.out.printf("Virtual Thread %d - error: %s%n", i, ex.getMessage());
21+
return null;
22+
});
23+
});
24+
});
25+
26+
while (!executor.isTerminated()) {
27+
;
28+
}
29+
}
30+
}
27.5 KB
Loading
19.7 KB
Loading
155 KB
Loading

java-19/img/platform-thread-count.png

66.6 KB
Loading
103 KB
Loading
167 KB
Loading
102 KB
Loading
Loading
322 KB
Loading

java-19/img/virtual-thread-count.png

88.9 KB
Loading
103 KB
Loading
119 KB
Loading
105 KB
Loading
155 KB
Loading
100 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Platform Thread vs Virtual Thread
2+
3+
## Start 10K Threads
4+
5+
The codes in `StressPlatformThread` and `StressVirtualThread` tries to show some different beetween platform and virtual thread using VisualVM to inspect the threads running.
6+
7+
### Platform Thread
8+
9+
The example _tries_ to start 10K [real] threads to sleep for 500ms.
10+
My Macbook 2015 couldn't handle 4K threads (we default JVM settings), after sometime it started to fail task submit and even GC were unable to run, then my macbook crashed.
11+
12+
My macOS currently has the default settings in maximum num of threads that can be created and threads per process:
13+
14+
![](img/macos-threads-settings.png)
15+
16+
Using 1K threads or changing to sleep for 100ms my macbook didn't crashed, so I could see VisualVM:
17+
18+
**1K Threads Started**
19+
20+
Looking at the Threads tab, we can see (by the scroll size) how many threads was started:
21+
22+
| Monitor | Timeline |
23+
|---|---|
24+
| ![](img/platform-thread-count.png) | ![](img/platform-thread-timeline.png) |
25+
26+
**10K sleeping 500ms**
27+
28+
When trying to run 10K threads sleeping for 500ms, I reached the macOS limit and things started to crash.
29+
30+
Before my Macbook restart, I could see:
31+
32+
![](img/platform-thread-500ms-count.png)
33+
34+
And right before restart:
35+
36+
![](img/platform-thread-500ms-error.png)
37+
38+
### Virtual Thread
39+
40+
The example starts 10K virtual threads to sleep for 30s.
41+
42+
The code runs easily as virtual threads is just sleeping, so ins't doing CPU-bound work.
43+
The JVM just starts them all and puts them aside util they wake up to really use the thread (just print).
44+
45+
Note that the default implementation is using ForkJoinPool to run the virtual threads work.
46+
47+
| Monitor | Timeline |
48+
|---|---|
49+
| ![](img/virtual-thread-count.png) | ![](img/virtual-thread-timeline.png) |
50+
51+
## Fibonacci (CPU-bound)
52+
53+
The CPU-bound work I was expecting Virtual Thread version to take more time to execute.
54+
55+
Worth note that the Platform Thread version used more resources (memory and OS threads) which caused more GC pressure.
56+
57+
### Platform Thread
58+
59+
Time taken to run:
60+
61+
```
62+
933.01s user 3.05s system 681% cpu 2:17.38 total
63+
```
64+
65+
| Monitor | Timeline |
66+
|---|---|
67+
| ![](img/platform-thread-fib-monitor.png) | ![](img/platform-thread-fib-timeline.png) |
68+
69+
### Virtual Thread
70+
71+
Time taken to run:
72+
73+
```
74+
957.94s user 3.43s system 614% cpu 2:36.55 total
75+
```
76+
77+
| Monitor | Timeline |
78+
|---|---|
79+
| ![](img/virtual-thread-fib-monitor.png) | ![](img/virtual-thread-fib-timeline.png) |
80+
81+
## Fibonacci and HTTP Request (CPU-bound and IO-bound)
82+
83+
100 fibonacci of 40 and request during 5s.
84+
85+
Again, note the resources used in Platform Thread version
86+
87+
Note the different between the memory and thread graphs for Platform and Virtual threads.
88+
89+
**My interpretation:**
90+
91+
The Platform Thread used real thread for each task (100), there were concorrency that caused all the calculation to end together, thus doing all the requesting also together (high slope in memory and thread count graph).
92+
93+
The Virtual Thread version had few real threads (pool with 10 threads), the OS [maybe] gave more time to taks allowing them to finish sooner, thus causing the requestings in small batches (smoothier slope in memory and thread count graph).
94+
95+
### Platform Thread
96+
97+
```
98+
104.54s user 1.20s system 448% cpu 23.598 total
99+
```
100+
101+
| Monitor | Timeline |
102+
|---|---|
103+
| ![](img/platform-thread-fibreq-monitor.png) | ![](img/platform-thread-fibreq-timeline.png) |
104+
105+
106+
### Virtual Thread
107+
108+
Time taken to run:
109+
110+
```
111+
77.55s user 0.89s system 403% cpu 19.458 total
112+
```
113+
114+
| Monitor | Timeline |
115+
|---|---|
116+
| ![](img/virtual-thread-fibreq-monitor.png) | ![](img/virtual-thread-fibreq-timeline.png) |

0 commit comments

Comments
 (0)