

1.Project Loom
在我们讨论Loom的各种概念之前,让我们先讨论一下当前Java中的并发模型。
2.Java的并发模型
目前,Thread 代表了Java中并发性的核心抽象。这个抽象以及其他并发API使得编写并发应用程序变得很容易。
然而,由于Java使用操作系统内核的线程来实现,它未能满足当今的并发要求。特别是有两个主要问题。
- 线程不能与领域的并发单位的规模相匹配。例如,应用程序通常允许高达上百万的交易、用户或会话。然而,内核所支持的线程数量却少得多。因此,为每个用户、交易或会话提供一个线程往往是不可行的。
- 大多数并发应用程序需要在每个请求的线程之间进行一些同步。由于这个原因,操作系统线程之间会发生昂贵的上下文切换。
解决这类问题的一个可能的办法是使用异步并发的API。常见的例子是CompletableFuture和RxJava。只要这样的API不阻塞内核线程,它就能在Java线程之上为应用程序提供一个更精细的并发结构*。*
另一方面,这样的API更难调试,也更难与传统的API整合。因此,需要一种独立于内核线程的轻量级并发结构。
3.任务和调度器
任何线程的实现,无论是轻量级还是重量级,都依赖于两个构造。
- 任务(也被称为延续)--一个指令序列,它可以为一些阻塞性操作而暂停自己的工作
- 调度器 - 用于将延续分配给CPU,并将CPU从暂停的延续中重新分配。
目前,Java依赖于操作系统对延续和调度器的实现。
现在,为了暂停一个延续,需要存储整个调用栈。同样地,在恢复时也要检索调用栈。由于操作系统对延续的实现包括本地调用堆栈和Java的调用堆栈,所以导致了一个沉重的足迹。
不过,更大的问题是使用操作系统的调度器。由于调度器在内核模式下运行,所以线程之间没有区别。而且它以同样的方式对待每一个CPU请求。
这种类型的调度对Java应用程序来说尤其不是最佳选择。
例如,考虑一个应用线程对请求进行一些操作,然后将数据传递给另一个线程进行进一步处理。在这里,最好将这两个线程都安排在同一个CPU上。但由于调度器对请求CPU的线程是不可知的,所以这是不可能保证的。
Project Loom建议通过用户模式的线程来解决这个问题**,这些线程依赖于Java运行时实现的连续和调度器,而不是操作系统的实现***。*
4.纤维(Fibers)
在OpenJDK最近的原型中,一个名为Fiber的新类与Thread类一起被引入库中。
由于计划中的Fibers库与Thread类似,用户的实现也应该保持类似。然而,有两个主要区别。
- Fiber 将把任何任务包裹在一个内部用户模式的延续中。这将允许任务在Java运行时而非内核中暂停和恢复。
- 将使用一个可插入的用户模式调度器*(例如ForkJoinPool*)。
让我们详细了解一下这两个项目。
5.延续
一个续程(或联合程序)是一个指令序列,它可以产生并由调用者在稍后阶段继续执行。
每个续程都有一个入口点和一个产生点。屈服点是它被中止的地方。每当调用者恢复继续执行时,控制就会返回到最后的屈服点。
重要的是要认识到**,这种暂停/恢复现在发生在语言的运行时而不是操作系统中**。因此,它防止了内核线程之间昂贵的上下文切换。
与线程类似,Project Loom旨在支持嵌套纤维。由于纤维内部依赖连续,它们也必须支持嵌套的连续。为了更好地理解这一点,考虑一个允许嵌套的Continuation 类。
Continuation cont1 = new Continuation(() -> {
Continuation cont2 = new Continuation(() -> {
//do something
suspend(SCOPE_CONT_2);
suspend(SCOPE_CONT_1);
});
});
如上所示,嵌套的连续体可以通过传递一个范围变量来暂停自己或任何一个包围的连续体*。* 由于这个原因,它们被称为作用域延续。
由于暂停一个连续体还需要它存储调用堆栈,所以在恢复连续体时增加轻量级的堆栈检索也是项目Loom的目标。
6.调度器
早些时候,我们讨论了操作系统调度器在同一CPU上调度相关线程的缺点。
尽管Project Loom的目标是允许带有纤维的可插拔调度器,但异步模式的ForkJoinPool 将被用作默认的调度器。
ForkJoinPool 工作在工作窃取算法上。因此,每个线程都维护一个任务deque,并从其头部执行任务。此外,任何空闲的线程都不会阻塞,等待任务,而是从另一个线程的deque的尾部拉取任务。
异步模式的唯一区别是,工作线程从另一个deque的头部窃取任务。
由于暂停一个延续也需要它存储调用堆栈,所以在恢复延续的同时增加轻量级的堆栈检索也是项目Loom的目标之一。
在这篇文章中,我们讨论了Java当前并发模型中的问题以及Project Loom提出的改变。
在此过程中,我们还定义了任务和调度器,并研究了Fibers和ForkJoinPool如何为Java提供一个使用内核线程的替代品。