openjdk8 项目结构_OpenJDK织机和结构化并发

openjdk8 项目结构

Project Loom是Hotspot Group赞助的项目之一,旨在向JAVA世界提供高吞吐量和轻量级的并发模型。 在撰写本文时,Loom项目仍在积极开发中,其API可能会更改。

为什么要织机?

每个新项目可能会出现的第一个问题是为什么?

为什么我们需要学习新的东西,它对我们有帮助? (如果确实如此)

因此,要专门针对Loom回答此问题,我们首先需要了解JAVA中现有线程系统如何工作的基础知识。

JVM内部产生的每个线程在OS内核空间中都有一个一对一的对应线程,并具有自己的堆栈,寄存器,程序计数器和状态。 每个线程的最大部分可能是堆栈,堆栈大小以兆字节为单位,通常在1MB到2MB之间。

因此,这些类型的线程在启动和运行时方面都很昂贵。 不可能在一台机器上产生1万个线程并期望它能正常工作。

有人可能会问为什么我们甚至需要那么多线程? 鉴于CPU只有几个超线程。 例如,CPU Internal Core i9总共有16个线程。

好吧,CPU并不是您的应用程序使用的唯一资源,任何没有I / O的软件都只会导致全球变暖!

一旦线程需要I / O,操作系统就会尝试为其分配所需的资源,并同时调度另一个需要CPU的线程。

因此,我们在应用程序中拥有的线程越多,我们就越可以并行利用这些资源。

一个非常典型的示例是Web服务器。 每个服务器都能在每个时间点处理数千个打开的连接,但是同时处理多个连接要么需要数千个线程,要么需要异步非阻塞代码( 我将**在接下来的几周内可能还会写另一篇文章来解释以及更多关于异步代码的信息**),正如前面提到的,成千上万的OS线程都不是您或OS都愿意的!

织机如何提供帮助?

作为Project Loom的一部分,引入了一种称为Fiber的新型线程。 光纤也称为虚拟线程绿色线程或用户线程,因为这些名称暗示完全由VM处理,并且OS甚至都不知道此类线程存在。 这意味着并非每个VM线程都需要在OS级别具有相应的线程! 虚拟线程可能被I / O阻塞或等待从另一个线程获取信号,但是,与此同时,其他虚拟线程也可以利用基础线程!

上图说明了虚拟线程和OS线程之间的关系。 虚拟线程可以简单地被I / O阻塞,在这种情况下,基础线程将被另一个虚拟线程使用。

这些虚拟线程的内存占用量将以千字节为单位,而不是兆字节。 如果需要,可以在生成它们之后扩展它们的堆栈,这样JVM不需要为它们分配大量内存。

因此,既然我们有一种非常轻巧的方式来实现并发,我们就可以重新考虑存在于Java经典线程中的最佳实践。

如今,在Java中实现并发最常用的结构是ExecutorService的不同实现。 它们具有非常方便的API,并且相对易于使用。 执行程序服务具有一个内部线程池,用于根据开发人员定义的特征来控制可以产生多少个线程。 该线程池主要用于限制应用程序创建的OS线程的数量,因为如上所述,它们是昂贵的资源,我们应该尽可能地重用它们。 但是现在可以生成轻量级虚拟线程了,我们也可以重新考虑使用ExecutorServices的方式。

结构化并发

结构化并发是一种编程范例,是一种编写易于读取和维护的并发程序的结构化方法。 如果代码对并发任务有明确的入口和出口点,则其主要思想与结构化编程非常相似,与启动可能比当前作用域更长的并发任务相比,对代码的推理要容易得多!

为了更清楚地了解结构化并发代码的外观,请考虑以下伪代码:

void notifyUser ( User user ) {
  try ( var scope = new ConcurrencyScope ()) {
   scope . submit ( () -> notifyByEmail ( user ));
   scope . submit ( () -> notifyBySMS ( user ));
  }
  LOGGER . info ( "User has been notified successfully" );
}

notifyUser方法应该通过电子邮件和SMS通知用户,一旦成功完成此方法将记录一条消息。 使用结构化并发,可以保证在两种通知方法都完成后立即写入日志。 换句话说,如果尝试范围在其中所有已启动的并发作业都完成了,那么它将完成!

注意:为了使示例简单,我们假设notifyByEmail和notifyBySMS在上面的示例中,在内部确实处理所有可能的极端情况,并始终使其通过。

JAVA的结构化并发

在本节中,我将通过一个非常简单的示例展示如何用JAVA编写结构化并发应用程序以及Fibers如何帮助扩展应用程序。

我们要解决的问题

想象一下,所有I / O绑定有1万个任务,而每个任务恰好需要100毫秒才能完成。 我们被要求编写高效的代码来完成这些工作。

我们使用下面定义的Job类来模仿我们的工作。

public class Job {
  public void doIt () {
    try {
      Thread . sleep ( 100 l );
    } catch ( InterruptedException e ) {
      e . printStackTrace ();
    }
  }
}

第一次尝试

在第一次尝试中,我们使用缓存线程池OS线程来编写它

public class ThreadBasedJobRunner implements JobRunner {
@Override
public long run ( List < Job > jobs ) {
  var start = System . nanoTime ();
  var executor = Executors . newCachedThreadPool ();
  for ( Job job : jobs ) {
    executor . submit ( job: : doIt );
  }

  executor . shutdown ();

  try {
    executor . awaitTermination ( 1 , TimeUnit . DAYS );
  } catch ( InterruptedException e ) {
    e . printStackTrace ();
    Thread . currentThread (). interrupt ();
  }
   var end = System . nanoTime ();
   long timeSpentInMS = Util . nanoToMS ( end - start );
   return timeSpentInMS ;
  }

}

在此尝试中,我们没有应用Loom项目中的任何内容。 只是一个缓存的线程池,以确保将使用空闲线程,而不是创建新线程。

让我们看看使用此实现可以运行10,000个作业所需的时间。 我使用下面的代码来查找运行速度最快的10个代码。 为简单起见,未使用任何微基准测试工具。

public class ThreadSleep {
  public static void main ( String [] args ) throws InterruptedException {
    List < Long > timeSpents = new ArrayList <>( 100 );
    var jobs = IntStream . range ( 0 , 10000 ). mapToObj ( n -> new Job ()). collect ( toList ());
    for ( int c = 0 ; c <= 100 ; c ++) {
      var jobRunner = new ThreadBasedJobRunner ();
      var timeSpent = jobRunner . run ( jobs );
      timeSpents . add ( timeSpent );

    }
    Collections . sort ( timeSpents );
    System . out . println ( "Top 10 executions took:" );
    timeSpents . stream (). limit ( 10 )
        . forEach ( timeSpent -> System . out . println ( "%s ms" . formatted ( timeSpent )));    
  }
}

我的机器上的结果是:

Top 10 executions took:  
694 ms  
695 ms  
696 ms  
696 ms  
696 ms  
697 ms  
699 ms  
700 ms  
700 ms  
700 ms

到目前为止,我们有一个代码,在最好的情况下,大约需要700毫秒才能在我的计算机上运行10,000个作业。 让我们这次使用Loom功能实现JobRunner。

第二次尝试(使用光纤)

在使用FibersVirtual Threads的实现中,我还将以结构化方式对并发进行编码。

public class FiberBasedJobRunner implements JobRunner {
  @Override
  public long run ( List < Job > jobs ) {
    var start = System . nanoTime ();
    var factory = Thread . builder (). virtual (). factory ();
    try ( var executor = Executors . newUnboundedExecutor ( factory )) {
      for ( Job job : jobs ) {
        executor . submit ( job: : doIt );
      }
    }

   var end = System . nanoTime ();
   long timeSpentInMS = Util . nanoToMS ( end - start );
   return timeSpentInMS ;
  }
}

也许关于此实现的第一个值得注意的事情是它的简洁性,如果将其与ThreadBasedJobRunner进行比较,您会发现该代码的行数更少! 主要原因是ExecutorService接口中的新更改现在扩展了Autocloseable ,因此,我们可以在try-with-resources范围中使用它。 所有提交的作业完成后,将执行try块之后的代码。

这正是我们用来在JAVA中编写结构化并发代码的主要结构。

上面代码中的另一个新事物是我们构建线程工厂的新方法。 Thread类有一个称为builder的新静态方法,可用于创建ThreadThreadFactory

该行代码正在创建一个创建虚拟线程的线程工厂

var factory = Thread . builder (). virtual (). factory ();

现在,让我们看看使用此实现可以运行10,000个作业所需的时间。

Top 10 executions took:  
121 ms  
122 ms  
122 ms  
123 ms  
124 ms  
124 ms  
124 ms  
125 ms  
125 ms  
125 ms

鉴于Project Loom仍在积极开发中,并且仍有提高速度的空间,但结果确实很棒。

不论是全部还是部分应用,都可以以最小的努力受益于Fibers! 唯一需要更改的是线程池的线程工厂 ,仅此而已!

具体来说,在此示例中,应用程序的运行速度提高了约6倍,但是,速度并不是我们在这里获得的唯一结果!

尽管我不想写有关使用Fibers大大减少了的应用程序的内存占用的信息,但是我强烈建议您在此访问本文的代码,并比较使用的内存量和每个实现占用的OS线程数! 您可以在此处下载Loom的官方早期试用版。

在接下来的帖子中,我将详细介绍Loom引入的其他API项目,以及我们如何将其应用于现实生活中的用例。

请不要犹豫,通过评论与我分享您的反馈!

翻译自: https://dev.to/psychoir/openjdk-loom-and-structured-concurrency-2e0e

openjdk8 项目结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值