OpenJDK Project Loom – OpenJDK项目的织布机

最后修改: 2019年 1月 29日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’ll take a quick look at Project Loom. In essence, the primary goal of Project Loom is to support a high-throughput, lightweight concurrency model in Java.

在本文中,我们将快速了解一下Project Loom。从本质上讲,Project Loom 的主要目标是支持 Java 中的高吞吐量、轻量级并发模型。

2. Project Loom

2.项目织机

Project Loom is an attempt by the OpenJDK community to introduce a lightweight concurrency construct to Java. The prototypes for Loom so far have introduced a change in the JVM as well as the Java library.

Loom项目是OpenJDK社区为在Java中引入轻量级并发结构而进行的一次尝试。到目前为止,Loom的原型已经在JVM以及Java库中引入了一个变化。

Although there is no scheduled release for Loom yet, we can access the recent prototypes on Project Loom’s wiki.

尽管Loom还没有预定发布,但我们可以在Project Loom的维基上访问最近的原型。

Before we discuss the various concepts of Loom, let’s discuss the current concurrency model in Java.

在我们讨论Loom的各种概念之前,让我们先讨论一下Java中当前的并发模型。

3. Java’s Concurrency Model

3.Java的并发模型

Presently, Thread represents the core abstraction of concurrency in Java. This abstraction, along with other concurrent APIs makes it easy to write concurrent applications.

目前,Thread代表了Java中并发的核心抽象。这一抽象以及其他并发API使得编写并发应用程序变得容易。

However, since Java uses the OS kernel threads for the implementation, it fails to meet today’s requirement of concurrency. There are two major problems in particular:

然而,由于Java使用OS内核线程来实现,因此无法满足当今对并发性的要求。特别是有两个主要问题。

  1. Threads cannot match the scale of the domain’s unit of concurrency. For example, applications usually allow up to millions of transactions, users or sessions. However, the number of threads supported by the kernel is much less. Thus, a Thread for every user, transaction, or session is often not feasible.
  2. Most concurrent applications need some synchronization between threads for every request. Due to this, an expensive context switch happens between OS threads.

A possible solution to such problems is the use of asynchronous concurrent APIs. Common examples are CompletableFuture and RxJava. Provided that such APIs don’t block the kernel thread, it gives an application a finer-grained concurrency construct on top of Java threads.

解决此类问题的可能办法是使用异步并发API。常见的例子有CompletableFutureRxJava。只要此类 API 不阻塞内核线程,它就能在 Java 线程之上为应用程序提供更精细的并发构造.

On the other hand, such APIs are harder to debug and integrate with legacy APIs. And thus, there is a need for a lightweight concurrency construct which is independent of kernel threads.

另一方面,这种API更难调试和与传统API集成。因此,需要一种独立于内核线程的轻量级并发结构。

4. Tasks and Schedulers

4.任务和调度器

Any implementation of a thread, either lightweight or heavyweight, depends on two constructs:

任何线程的实现,无论是轻量级还是重量级,都取决于两个结构。

  1. Task (also known as a continuation) – A sequence of instructions that can suspend itself for some blocking operation
  2. Scheduler – For assigning the continuation to the CPU and reassigning the CPU from a paused continuation

Presently, Java relies on OS implementations for both the continuation and the scheduler.

目前,Java的延续和调度器都依赖于操作系统的实现

Now, in order to suspend a continuation, it’s required to store the entire call-stack. And similarly, retrieve the call-stack on resumption. Since the OS implementation of continuations includes the native call stack along with Java’s call stack, it results in a heavy footprint.

现在,为了暂停一个延续,需要存储整个调用堆栈。同样地,在恢复时要检索调用栈。由于操作系统对延续的实现包括本地调用堆栈和Java的调用堆栈,这就导致了一个沉重的足迹

A bigger problem, though, is the use of OS scheduler. Since the scheduler runs in kernel mode, there’s no differentiation between threads. And it treats every CPU request in the same manner.

但更大的问题是对操作系统调度器的使用。由于调度器在内核模式下运行,所以线程之间没有区别。而且它以同样的方式对待每一个CPU请求。

This type of scheduling is not optimal for Java applications in particular.

这种类型的调度对Java应用程序来说尤其不是最佳选择。

For example, consider an application thread which performs some action on the requests and then passes on the data to another thread for further processing. Here, it would be better to schedule both these threads on the same CPU. But since the scheduler is agnostic to the thread requesting the CPU, this is impossible to guarantee.

例如,考虑一个应用程序线程,它对请求执行一些操作,然后将数据传递给另一个线程作进一步处理。在这里,最好将这些线程都安排在同一个CPU上。但由于调度器对请求CPU的线程是不可知的,所以这是不可能保证的。

Project Loom proposes to solve this through user-mode threads which rely on Java runtime implementation of continuations and schedulers instead of the OS implementation.

Project Loom提议通过用户模式线程来解决这个问题,该线程依赖于Java运行时的延续和调度器的实现,而不是操作系统的实现.

5. Fibers

5.纤维

In the recent prototypes in OpenJDK, a new class named Fiber is introduced to the library alongside the Thread class.

在OpenJDK最近的原型中,一个名为Fiber的新类与Thread类一起被引入库中。

Since the planned library for Fibers is similar to Thread, the user implementation should also remain similar. However, there are two main differences:

由于计划中的Fibers库与Thread相似,用户的实现也应该保持相似。然而,有两个主要区别。

  1. Fiber would wrap any task in an internal user-mode continuation. This would allow the task to suspend and resume in Java runtime instead of the kernel
  2. A pluggable user-mode scheduler (ForkJoinPool, for example) would be used

Let’s go through these two items in detail.

让我们详细了解一下这两项内容。

6. Continuations

6.持续性

A continuation (or co-routine) is a sequence of instructions that can yield and be resumed by the caller at a later stage.

延续(或联合程序)是一个指令序列,它可以产生并由调用者在稍后阶段继续执行。

Every continuation has an entry point and a yield point. The yield point is where it was suspended. Whenever the caller resumes the continuation, the control returns to the last yield point.

每个延续都有一个入口点和一个屈服点。屈服点是它被中止的地方。每当调用者恢复延续时,控制就会返回到最后的屈服点。

It’s important to realize that this suspend/resume now occurs in the language runtime instead of the OS. Therefore, it prevents the expensive context switch between kernel threads.

重要的是要认识到这种暂停/恢复现在发生在语言运行时而不是操作系统中。因此,它可以防止内核线程之间昂贵的上下文切换。

Similar to threads, Project Loom aims to support nested fibers. Since fibers rely on continuations internally, it must also support nested continuations. To understand this better, consider a class Continuation that allows nesting:

与线程类似,Project Loom旨在支持嵌套的纤维。由于纤维内部依赖连续,所以它也必须支持嵌套的连续。为了更好地理解这一点,请考虑一个允许嵌套的Continuation类。

Continuation cont1 = new Continuation(() -> {
    Continuation cont2 = new Continuation(() -> {
        //do something
        suspend(SCOPE_CONT_2);
        suspend(SCOPE_CONT_1);
    });
});

As shown above, the nested continuation can suspend itself or any of the enclosing continuations by passing a scope variableFor this reason, they are known as scoped continuations.

如上所示,嵌套的延续可以通过传递一个范围变量来暂停自己或任何一个包围的延续。因此,它们被称为范围延续。

Since suspending a continuation would also require it to store the call stack, it’s also a goal of project Loom to add lightweight stack retrieval while resuming the continuation.

由于暂停一个延续也需要它存储调用堆栈,所以在恢复延续时增加轻量级的堆栈检索也是Loom项目的一个目标。

7. Scheduler

7.调度员

Earlier, we discussed the shortcomings of the OS scheduler in scheduling relatable threads on the same CPU.

早些时候,我们讨论了操作系统调度器在同一CPU上调度相关线程的缺点。

Although it’s a goal for Project Loom to allow pluggable schedulers with fibers, ForkJoinPool in asynchronous mode will be used as the default scheduler. 

虽然Project Loom的目标是允许带有纤维的可插拔调度器,但异步模式的ForkJoinPool将被用作默认调度器。

ForkJoinPool works on the work-stealing algorithm. Thus, every thread maintains a task deque and executes the task from its head. Furthermore, any idle thread does not block, waiting for the task and pulls it from the tail of another thread’s deque instead. 

ForkJoinPool工作窃取算法上运作。因此,每个线程都维护一个任务deque,并从其头部执行任务。此外,任何空闲的线程都不会阻塞,等待任务,而是从另一个线程的deque尾部拉取任务。

The only difference in asynchronous mode is that the worker threads steal the task from the head of another deque.

异步模式的唯一区别是,工作线程从另一个deque的头部窃取任务

ForkJoinPool adds a task scheduled by another running task to the local queue. Hence, executing it on the same CPU. 

ForkJoinPool 将另一个正在运行的任务安排的任务添加到本地队列。因此,在同一个CPU上执行它。

8. Conclusion

8.结语

In this article, we discussed the problems in Java’s current concurrency model and the changes proposed by Project Loom.

在这篇文章中,我们讨论了Java当前并发模型中存在的问题以及Project Loom提出的改变。

In doing so, we also defined tasks and schedulers and looked at how Fibers and ForkJoinPool could provide an alternative to Java using kernel threads.

在此过程中,我们还定义了任务和调度器,并研究了Fibers和ForkJoinPool如何为Java提供一个使用内核线程的替代品。