Features track

Java 19: JEP 425: Virtual Threads (preview)

Java 19: JEP 428: Structured concurrency (incubator)

👍Awesome feature track for all versions with examples

  • Pattern Matching for switch supporting type patterns and guarded patterns (Preview 🔍) JDK 18 JDK 17

    String formatted = switch (o) {
        case Integer i && i > 10 -> String.format("a large Integer %d", i);
        case Integer i -> String.format("a small Integer %d", i);
        case Long l    -> String.format("a Long %d", l);
        default        -> o.toString();
    };

    → Related: Inside Java - Episode 17 “Pattern Matching for switch” with Gavin Bierman

  • Sealed Classes can restrict which other classes may extend them JDK 17 (Preview 🔍 in JDK 16 JDK 15)

    public abstract sealed class Shape
        permits Circle, Rectangle {...}
    
    public final class Circle extends Shape {...} // OK
    public final class Rectangle extends Shape {...} // OK
    public final class Triangle extends Shape {...} // Compile error
    
    // No need for default case if all permitted types are covered
    double area = switch (shape) {
        case Circle c    -> Math.pow(c.radius(), 2) * Math.PI
        case Rectangle r -> r.a() * r.b()
    };
  • Record Classes, terse syntax to define immutable DTOs JDK 16 (Preview 🔍 in JDK 15 JDK 14)

    record Point(int x, int y) { }
    
    var point = new Point(1, 2);
    point.x(); // returns 1
    point.y(); // returns 2

    → Related: Inside Java - Episode 14 “Records Serialization” with Julia Boes and Chris Hegarty

  • Pattern Matching for instanceof to eliminate the need for explicit casts after a type check JDK 16 (Preview 🔍 in JDK 15 JDK 14)

    if (obj instanceof String s && s.length() > 5) {
        System.out.println("obj is a String with more than 5 characters: " + s.toUpperCase());
    }
  • Text Blocks JDK 15 (Preview 🔍 in JDK 14 JDK 13)

    String html = """
                <html>
                    <body>
                        <p>Hello, world</p>
                    </body>
                </html>
                """;
  • Helpful NullPointerExceptions describing precisely which variable was null JDK 15 (Enabled with -XX:+ShowCodeDetailsInExceptionMessages in JDK 14)

    a.b.c.i = 99;
    ---
    Exception in thread "main" java.lang.NullPointerException:
          Cannot read field "c" because "a.b" is null
  • Switch Expressions JDK 14 (Preview 🔍 in JDK 12 JDK 13)

    int numLetters = switch (day) {
        case MONDAY, FRIDAY, SUNDAY -> 6;
        case TUESDAY                -> 7;
        default      -> {
          String s = day.toString();
          int result = s.length();
          yield result;
        }
    };
  • Introduction of var to make local variable declarations less ceremonious JDK 11 (Without lambda support in JDK 10)

    var greeting = "Hello World!";
  • Opt-in and backwards-compatible Module System to avoid ClassDefNotFoundErrors at runtime and create internal APIs JDK 9 (Project Jigsaw)

    module hu.advancedweb.helloworld {
        requires hu.advancedweb.somedependency;
        exports hu.advancedweb.hello
    }
  • Private methods in interfaces JDK 9 (Milling Project Coin)

  • Diamond operator for anonymous inner classes JDK 9 (Milling Project Coin)

  • Try-with-resources allows effectively final variables JDK 9 (Milling Project Coin)

  • @SafeVargs on private instance methods JDK 9 (Milling Project Coin)

  • No deprecation warnings on import statements JDK 9

Details

JEP 425 has three goals:

  • Enable server applications written in the simple thread-per-request style to scale with near-optimal hardware utilization

  • Enable existing code that uses the java.lang.Thread API to adopt virtual threads with minimal change

  • Enable easy troubleshooting, debugging, and profiling of virtual threads with existing JDK tools

Non-Goals

  • It is not a goal to remove the traditional implementation of threads, or to silently migrate existing applications to use virtual threads.

  • It is not a goal to change the basic concurrency model of Java.

  • It is not a goal to offer a new data parallelism construct in either the Java language or the Java libraries. The Stream API remains the preferred way to process large data sets in parallel.

Before virtual threads, each thread in a Java application is mapped directly to an operating system (OS) thread. This makes sense because the JVM does not need to be concerned with things like direct scheduling and context switching of the threads. However, it does become an issue for certain common types of applications. Take the example of a web server that handles connections that do nothing for long periods while a user decides what to do next. In this case, the Java thread blocks, but this also prevents the OS thread from doing anything. When the number of simultaneous connections grows to a very high number, we can exhaust the available OS threads, even though many of these threads are doing nothing.

Virtual threads solve this by having multiple Java threads map to a single OS thread. As a result, OS threads can now be used efficiently to service Java threads that need to do work, and an application can support literally millions of simultaneous connections. (This is just one example of how virtual threads can be used; there are plenty of others).

In current implementation each Java thread is a wrapper around OS thread. Instead of handling a request on one thread from start to finish, request-handling code returns its thread to a pool when it waits for an I/O operation to complete so that the thread can service other requests. This fine-grained sharing of threads — in which code holds on to a thread only when it performs calculations, not when it waits for I/O — allows a high number of concurrent operations without consuming a high number of threads. While it removes the limitation on throughput imposed by the scarcity of OS threads, it comes at a high price: It requires what is known as an asynchronous programming style

Application code in the thread-per-request style can run in a virtual thread for the entire duration of a request, but the virtual thread consumes an OS thread only while it performs calculations on the CPU. The result is the same scalability as the asynchronous style, except it is achieved transparently: When code running in a virtual thread calls a blocking I/O operation in the java.* API, the runtime performs a non-blocking OS call and automatically suspends the virtual thread until it can be resumed later.

Virtual threads are not faster threads — they do not run code any faster than platform threads. They exist to provide scale (higher throughput), not speed (lower latency).

Virtual threads can significantly improve application throughput when

  • The number of concurrent tasks is high (more than a few thousand), and

  • The workload is not CPU-bound, since having many more threads than processor cores cannot improve throughput in that case.

Threads, which are implemented as OS threads, the JDK relies on the scheduler in the OS. By contrast, for virtual threads, the JDK has its own scheduler. Rather than assigning virtual threads to processors directly, the JDK's scheduler assigns virtual threads to platform threads (this is the M:N scheduling of virtual threads mentioned earlier). The platform threads are then scheduled by the OS as usual.

Last updated