f : futures)
+ System.out.print(f.get() + " ");
+ System.out.println();
+ service.close();
+ }
+
+ private static HttpClient client = HttpClient.newHttpClient();
+
+ private static final Semaphore SEMAPHORE = new Semaphore(20);
+
+ public static String get(String url) {
+ try {
+ var request = HttpRequest.newBuilder().uri(new URI(url)).GET().build();
+ SEMAPHORE.acquire();
+ try {
+ Thread.sleep(100);
+ return client.send(request, HttpResponse.BodyHandlers.ofString()).body();
+ } finally {
+ SEMAPHORE.release();
+ }
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ var rex = new RuntimeException();
+ rex.initCause(ex);
+ throw rex;
+ }
+ }
+}
+```
+
+
+## Pinning
+
+The virtual thread scheduler mounts virtual threads onto carrier threads. By default, there are as many carrier threads as there are CPU cores. You can tune that count with the `jdk.virtualThreadScheduler.parallelism` VM option.
+
+When a virtual thread executes a blocking operation, it is supposed to be unmounted from its its carrier thread, which can then execute a different virtual thread. However, there are situations where this unmounting is not possible. In some situations, the virtual thread scheduler will compensate by starting another carrier thread. For example, in JDK 21, this happens for many file I/O operations, and when calling `Object.wait`. You can control the maximum number of carrier threads with the `jdk.virtualThreadScheduler.maxPoolSize` VM option.
+
+A thread is called *pinned* in either of the two following situations:
+
+1. When executing a `synchronized` method or block
+2. When calling a native method or foreign function
+
+Being pinned is not bad in itself. But when a pinned thread blocks, it cannot be unmounted. The carrier thread is blocked, and, in Java 21, no additional carrier thread is started. That leaves fewer carrier threads for running virtual threads.
+
+Pinning is harmless if `synchronized` is used to avoid a race condition in an in-memory operation. However, if there are blocking calls, it would be best to replace `synchronized` with a `ReentrantLock`. This is of course only an option if you have control over the source code.
+
+To find out whether pinned threads are blocked, start the JVM with one of the options
+
+```shell
+-Djdk.tracePinnedThreads=short
+-Djdk.tracePinnedThreads=full
+```
+
+You get a stack trace that shows when a pinned thread blocks:
+
+```shell
+...
+org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) <== monitors:1
+...
+```
+
+Note that you get only one warning per pinning location!
+
+Alternatively, record with Java Flight Recorder, view with your favorite mission control viewer, and look for `VirtualThreadPinned` and `VirtualThreadSubmitFailed` events.
+
+The JVM will eventually be implemented so that `synchronized` methods or blocks no longer lead to pinning. Then you only need to worry about pinning for native code.
+
+The following sample program shows pinning in action. We launch a number of virtual threads that sleep in a synchronized method, blocking their carrier threads. A number of virtual threads are added that do no work at all. But they can't be scheduled because the carrier thread pool has been completely exhausted. Note that the problem goes away when you
+
+* use a `ReentrantLock`
+* don't use virtual threads
+
+```java
+import java.util.concurrent.*;
+import java.util.concurrent.locks.*;
+
+public class PinningDemo {
+ public static void main(String[] args) throws InterruptedException, ExecutionException {
+ ExecutorService service =
+ Executors.newVirtualThreadPerTaskExecutor();
+ // Executors.newCachedThreadPool();
+
+ final int TASKS = 20;
+ long start = System.nanoTime();
+ for (int i = 1; i <= TASKS; i++) {
+ service.submit(() -> block());
+ // service.submit(() -> rblock());
+ }
+ for (int i = 1; i <= TASKS; i++) {
+ service.submit(() -> noblock());
+ }
+ service.close();
+ long end = System.nanoTime();
+ System.out.printf("%.2f%n", (end - start) * 1E-9);
+ }
+
+ public static synchronized void block() {
+ System.out.println("Entering block " + Thread.currentThread());
+ LockSupport.parkNanos(1_000_000_000);
+ System.out.println("Exiting block " + Thread.currentThread());
+ }
+ private static Lock lock = new ReentrantLock();
+ public static void rblock() {
+ lock.lock();
+ try {
+ System.out.println("Entering rblock " + Thread.currentThread());
+ LockSupport.parkNanos(1_000_000_000);
+ System.out.println("Exiting rblock " + Thread.currentThread());
+ } finally {
+ lock.unlock();
+ }
+ }
+ public static void noblock() {
+ System.out.println("Entering noblock " + Thread.currentThread());
+ LockSupport.parkNanos(1_000_000_000);
+ System.out.println("Exiting noblock " + Thread.currentThread());
+ }
+}
+```
+
+
+## Thread Locals
+
+A *thread-local variable* is an object whose `get` and `set` methods access a value that depends on the current thread. Why would you want such a thing instead of using a global or local variable? The classic application is a service that is not threadsafe, such as `SimpleDateFormat`, or that would suffer from contention, such as a random number generator. Per-thread instances can perform better than a global instance that is protected by a lock.
+
+Another common use for thread locals is to provide “implicit” context, such as a database connection, that is properly configured for each task. Instead of passing the context from one method to another, the task code simply reads the thread-local variable whenever it needs to access the database.
+
+Thread locals can be a problem when migrating to virtual threads. There will likely be far more virtual threads than threads in a thread pool, and now you have many more thread-local instances. In such a situation, you should rethink your sharing strategy.
+
+To locate uses of thread locals in your app, run with the VM flag `jdk.traceVirtualThreadLocals`. You will get a stack trace when a virtual thread mutates a thread-local variable.
+
+
+## Conclusion
+
+* Use virtual threads to increase throughput when you have many tasks that mostly block on network I/O
+* The primary benefit is the familiar “synchronous” programming style, without callbacks
+* Don't pool virtual threads; use other mechanisms for rate limiting
+* Check for pinning and mitigate if necessary
+* Minimize thread-local variables in virtual threads
diff --git a/app/templates/pages/learn/index.html b/app/templates/pages/learn/index.html
index bafeb2c..e3a8eeb 100644
--- a/app/templates/pages/learn/index.html
+++ b/app/templates/pages/learn/index.html
@@ -28,6 +28,18 @@ Running Your First Java Application
+
+
Staying Aware of New Features
+
+
+
Getting to Know the Language
@@ -41,6 +53,42 @@ Getting to Know the Language
+
+
+
+
Organizing your Application
+
+
+
+
+
Getting to know the JVM
+
+
+
{{ contents | safe }}
diff --git a/package-lock.json b/package-lock.json
index 7a9c501..6029cab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,7 +34,8 @@
},
"devDependencies": {
"dotenv": "^16.0.3",
- "gulp-markdown-toc": "^1.1.0"
+ "gulp-markdown-toc": "^1.1.0",
+ "gulp-util": "^3.0.8"
}
},
"node_modules/-": {
diff --git a/package.json b/package.json
index 8fa098a..62f4815 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
},
"devDependencies": {
"dotenv": "^16.0.3",
- "gulp-markdown-toc": "^1.1.0"
+ "gulp-markdown-toc": "^1.1.0",
+ "gulp-util": "^3.0.8"
}
}