Project Loom: Virtual threads in Java 19

Original name

Projekt Loom: virtuální vlákna v Java 19

Author(s)

Miroslav Sevelda

Length

43:11

Date

12-11-2023

Language

Czech 🇨🇿

Rating

⭐⭐⭐⭐⭐

  • ✅ I don’t know if I am impressed by the speaker or the topic. Disadvantages and patterns were well-explained.

"The entire JVM was reworked to support the virtual threads."


History:

  • Java 1 - Basic support for multithreading

  • Java 5 - Thread pools, executor services, futures, callable

  • Java 7 - Fork/join framework (velmi důležitý pro virt. vláken jako scheduler pro mapování na fyzická)

  • Java 8 - Completable futures, lambda, streams

  • Java 9 - Reactive streams (against IO-bound threads)

  • Java 19 - Virtual threads

Current model: In the current model, a thread in the JVM always means there is a corresponding OS thread linked. This model will be still available and unchanged with the Project Loom.

Project Loom: (JEP 425) It brings a new solution of massive parallelism and introduces virtual threads that are invisible from the point of view of the OS. It means it’s possible to create theoretically a billion of threads without saturating the OS. This principle is similar to what Python uses: virtual thread synchronization into a single one. This project doesn’t introduce virtual threads only. Motto: Easy-to-use, high-throughput, lightweight concurrency, and new programming models.

Virtual thread

It is a planned and manager thread visible only on the JVM level and is similar to the [green thread](https://en.wikipedia.org/wiki/Green_thread) concept and analogous to the currently unused POSIX threads. The solution was to return to the M:N model: In this case, the JVM platform thread is mapped to the OS thread and a huge amount of virtual threads. Context switch through a scheduler mounts the virtual thread to the JVM platform thread, which is a single OS thread.

Terminology

  • Virtual thread: - Invisible in the OS.

  • Platform thread: - JVM thread mapped to the OS thread.

  • Carried thread - Platform thread bound to the virtual thread as it is not possible to run a virtual thread with no platform thread.

  • Thread mounting - Process when the virtual thread is assigned to platform one.

  • Thread unmounting - Process when the virtual thread is unassigned from platform one. It happens automatically as soon as the virtual thread invokes a blocking operation.

Disadvantages of the current model

  • JVM thread is bound to the OS thread, which means the thread management is above the JVM, and thread creation is expensive.

  • Inflexible memory allocation for the thread stack memory, which is not under the control of the JVM.

  • Problematic support for massive parallelism and not optimal for IO-bound threads that are not computing in a blocked state.

Advantages in the new model

  • Virtual threads are inexpensive, so it’s possible to create billion of virtual threads, basically as much as we need without the OS and resources limits.

  • Scheduling and memory management are under the JVM control and the GC.

  • Alternative to the Async-IO and reactive approaches.

  • No dangerous operations like suspending or stopping threads since the virtual threads are un/mounted automatically.

Disadvantages of the new model

  • The debugging can become harder as it is no longer possible to use the debugging tools on the OS level.

  • It is too soon for comprehensible conclusions.

New API

Only a few changes were introduced for minimal interference to the existing API for sake of an easy transition to virtual threads. The new solution completes the current API design and the virtual thread is still an instance of Thread as well as the platform thread.

Each virtual thread is of a type DAEMON and has fixed NORM_PRIORITY that cannot be changed (as it never worked correctly and synchronization primitives were always a better way to go).

The virtual thread scheduling is implemented with the existing ForkJoinPool in the FIFO mode that implements the "work stealing", though it is partially customizable and it is possible to use a custom scheduler (but why). It is great that virtual threads are built on the existing and well-known implementation.

New factory methods

The old implementation is unchanged, and it is needed to use factory methods to create a virtual thread.

  • Create a virtual thread:

    Thread vt = Thread.ofVirtual()
  • Create a platform thread:

    Thread pt = Thread.ofPlatform()
  • Create and start a virtual thread:

    Thread vt = Thread.startVirtualThread()

Fluent style

  • Thread vt = Thread.ofVirtual().name("virtual").unstarted(runnable);
  • Thread vt = Thread.ofPlatform().name("platform").unstarted(runnable);

Builder and Factory style

  • boolean isPlatformVirtual = Thread.ofPlatform().isVirtual(); // false
  • boolean isVirtualVirtual = Thread.virtual().isVirtual(); // true
  • boolean isVirtualDaemon = Thread.virtual().isDaemon(); // true

ExecutorService

  • ExecutorService service = Executors.newVirtualThreadPerTaskExecutor();

Anti-patterns

The virtual threads should not be pooled as long as they are lightweight and the pool management becomes an overhead compared to threads creation. However, there is a new dedicated thread pool ExecutorService, though it’s better to just create a thread and let the system handle it.

Do not use priorities with virtual threads.