created on | October 6, 2022 |
general availability on 20 March 2018
JEP | state | summary /remark |
---|---|---|
JEP 405: Record Patterns | preview | introduces record patterns to deconstruct record values. Record patterns and type patterns can be nested to for a declarative and composable form of data navigation and processing. |
JEP 422: Linux/RISC-V Port | standard | port the JDK to Linux/RISC-V. |
JEP 424: Foreign Function & Memory API | preview | eases the access for devs to code and data on the same machine as the JVM but outside of the JVM, without the drawbacks of JNI. |
JEP 425: Virtual Threads | preview | introduces lightweight threads called virtual threads to the Java platform. |
JEP 426: Vector API | fourth incubator | provides a second iteration of an incubator module, jdk.incubator.vector, to express vector computations that reliably compile at runtime to optimal vector hardware instructions on supported CPU architectures and thus achieve superior performance to equivalent scalar computations. |
JEP 427: Pattern Matching for switch | third preview | introduces pattern matching for switch expressions and statements, along with extensions to the language of patterns. See comments on JEP 406: Pattern Matching for switch (Preview). |
JEP 428: Structured Concurrency | incubator | simplifies multithreaded programming by introducing an API for structured concurrency. Structured concurrency treats multiple tasks running in different threads as a single unit of work, thereby streamlining error handling and cancellation, improving reliability, and enhancing observability. |
none
none
none
In JDK 16, the instanceof operator is extended to take a type pattern and perform pattern matching (see also JEP 394: Pattern Matching for instanceof and comments on ‘JEP 305: Pattern Matching for instanceof (Preview)').
Records, available since JDK 16, are transparent data containers. Record patterns are an extension of pattern matching for instanceof to match records:
Here, is a record pattern.
pattern matching scales to match more complicated object graphs. I.e., for the following declarations:
the color of the upper left point can be extracted (and printed on System.out) with:
Record patterns for generic record classes must use the generic type. I.e. for the following declaration:
the following two methods are correct:
whereas The following two methods are not correct and will result in compile-time errors:
Since both switch expressions and pattern switch statements must both be exhaustive, the switch block must have clauses that deal with all possible values of the selector expression. For pattern labels this is determined by analysis of the types of the patterns; i.e., the case label case Fruit f matches values of type Fruit and all possible subtypes of Fruit.
I.e. for the following two class and the record declarations:
and a record Pair<A> p1 of the type record Pair<T>(T x, T y) {}, the following switch not exhaustive, because it has no match for a Pair containing two types of A:
For the following declaration of an interface and two classes implementing the interface and the declaration of a record Pair<I> p2;:
this switch statement is valid, because I is sealed, so C and D cover all possible instances:
Traditionally, Java has used the java.lang.Thread platform threads. A platform thread is a thin wrapper around an OS thread and occupies an OS thread for the entire lifetime of a thread. Or, in other terms, it monopolizes an OS thread. However, OS threads are limited and a large number of platform thrads can exhaust the available OS threads.
The concept of virtual threads is similar to the concept of virtual memory. A virtual thread doesn’t monopolize an underlaying OS thread, instead there is an M:N relation, where a large number of virtual threads run on a much smaller number of OS threads. A virtual thread occupies an OS thread only while it performs calculation on the CPU.
The relatively scarce number of available OS threads eventually lead devs to abandon the thread-per-request programming style in favour of the asynchronous style in cases where a large number (i.e. in a magnitude of some thousands) of requests would run simultaneously.
In the asynchronous programming style, instead of handling a request in one thread from start to finish, the requests utilize threads from a thread pool. The pooled threads are occupied when performing calculation on the CPU and released back to the pool when waiting for I/O operations to complete.
Necessarily, here the requests have to be broken up into small units of code. In the asynchronous style, I/O operations do not wait for completion and then return the result but signal their completion to a callback. The logic for handling the request is broken up into small units, often written as lambdas. Those chunks of code are composed into a sequential pipeline. This is where the reactive frameworks come from. This programming style comes with a set of problems:
Virtual threads have been introduced to allow applications to scale while using the thread-per-request style.
In the following example, 10k virtual threads are created. The task executed in each thread is to sleep for second. The ExecutorService creates and submits the tasks and then waits for all of them to complete. The JDK will run the code on a small number of OS threads, possibly on only one thread.
Thread-locals can be used in virtual threads the same way as they are used in plattform threads. However, care should be taken when using thread-locals in virtual threads because of the possibly vast number of virtual threads.
You can use JDK debuggers and the JDK Flight Recorder on virtual threads the same way as you can use them on platform threads.
Thread dumps are another story. The JDK’s traditional thread dump tool jstack and jcmd present threads in a flat list. This is suitable up to say a few hundred of threads, but not for thousands let alone millions of threads.
For this reason, a new kind of thread dump is introduced in jcmd to present virtual threads alongside platform threads, grouped in a meaningful way. jcmd supports the output of thread dumps in JSON format, which eases analyzation and visualization by suitable tools:
There are some differences in thread dumps for virtual threads as opposed to platform threads:
Relationships among threads can be shown if programs use structured concurrency, which is also introduced in JDK 19.
Since virtual threads are not tied to any OS thread like platform threads, virtual threads are invisible from the OS perspective.
Imagine a server application that serves incoming requests and returns a (quite simple) response. Here is the single threaded version:
There are few thing worth noting:
Obviously, it is desirable to have both tasks getString() and getInt() running simultaneously to utilize computing power as effective as possible and reduce the time for handle() to complete the request. Here is the (somewhat simplicistic) multithreaded version:
Again, there are a few things here worth noting:
So we have (at least) three problems here:
To avoid these problems one has to manage the lifecycle of the threads for the tasks with try-with-resources and try-finally, but this is tricky. Both of which is due to the fact that the ExecutorService and Future allow unrestricted patterns of concurrency.
Here’s the handle() method using the StructuredTaskScope from structured concurrency:
Here, we have the following features:
OpenJDK JDK 19 Feature List and Schedule