Java-三分钟速成-揭秘多线程编程新范式-虚拟线程 (java三目表达式)
背景
虚拟线程是 Java 语言中的一种轻量级线程,可以减少编写、维护和调试高吞吐量并发应用程序的工作量。虚拟线程的详细背景介绍可以在 JEP 444 中找到。
平台线程
在操作系统中,线程是其可调度的最小处理单元。同一时刻会有很多线程同时运行,但它们之间基本相互独立运行。在 Java 中,操作线程的相关 API 都在
java.lang.Thread
类中。
在 Java 21 及以后版本中,线程有两种:平台线程和虚拟线程。平台线程是操作系统线程的简单包装器。在平台线程上运行的 Java 代码,在其底层逻辑上,其实就是运行在操作系统的线程上,并且平台线程在其整个生命周期内都与操作系统线程一一对应。
因此,在 Java 项目中,可用平台线程的数量依赖于操作系统线程的数量。根据操作系统和 JVM 启动参数配置的不同,创建一个平台线程默认会消耗 1MB 的空间。因此,平台线程的资源相当宝贵,我们无法大量创建平台线程。
虚拟线程
与平台线程一样,虚拟线程也是
java.lang.Thread
的一个实例对象。但是,虚拟线程并不依赖于特定的操作系统线程。虚拟线程底层仍然在操作系统的线程上运行代码。
但与平台线程不同的是,在平台线程中运行的代码调用阻塞 I/O 操作时,JVM 就会挂起该平台线程(也就会挂起操作系统线程),直到阻塞 I/O 可以恢复为止。而在虚拟线程中调用阻塞 I/O 操作时,JVM 虽然也会挂起该虚拟线程,但是与平台线程不同的是,被挂起虚拟线程关联的操作系统线程是可以为其他虚拟线程继续服务的。
虚拟线程的实现方式与虚拟内存类似。为了模拟大量内存,操作系统将较大的虚拟地址空间映射到有限的 RAM。同样,为了模拟大量线程,Java 运行时将大量虚拟线程映射到少量操作系统线程。
因此,与平台线程消耗的资源很多不同,虚拟线程在使用时只需要很少的内存资源。单个 JVM 就可以轻松创建数百万个虚拟线程。但是,在使用虚拟线程时,建议调用堆栈不要过深,只执行单个 HTTP 客户端调用或单个 JDBC 查询即可。尽管虚拟线程支持线程局部变量和可继承的线程局部变量,但我们应该仔细考虑后再使用它们,因为单个 JVM 可能运行数百万个虚拟线程。
虚拟线程适合用于运行有大量阻塞 I/O 操作的任务,而不是长时间运行的 CPU 密集型任务。
为什么建议使用虚拟线程?
在高吞吐量、高并发应用程序中推荐使用虚拟线程,尤其是那些包含大量并发任务且大部分时间都在等待阻塞 I/O 操作的应用程序中。使用虚拟线程可以让应用程序就算使用同步阻塞 API,也能对操作系统的硬件资源利用达到近乎完美水平。
可以说,虚拟线程的引入,以后程序员就算是使用 Java 中同步阻塞 API 也可以开发出高性能、高吞吐量的应用程序。
结论
虚拟线程是一种轻量级线程,带给了程序员一种新的编程体验。编写高性能、高吞吐量应用程序时使用虚拟线程配合同步阻塞 API 就能得到与异步编程模型相媲美的性能,并且避免了异步编程模型的编程复杂度。比如在 SpringBoot 3.2 中,就可以通过启用虚拟线程带来可观的性能提升。
java 创建多线程
Java 多线程的同步依靠的是对象锁机制,这个问题需要我们不断的学习相关的问题。 下面我们就来详细的学习下如何才能更好的进行具体内容的使用。 synchronized关键字的背后就是利用了封锁来实现对共享资源的互斥访问。 下面以一个简单的实例来进行对比分析。 实例要完成的工作非常简单,就是创建10个线程,每个线程都打印从0到99这100个数字,我们希望线程之间不会出现交叉乱序打印,而是顺序地打印。 先来看第一段代码,这里我们在run()方法中加入了synchronized关键字,希望能对run方法进行互斥访问,但结果并不如我们希望那样,这是因为这里synchronized锁住的是this对象,即当前运行线程对象本身。 Java 多线程代码中创建了10个线程,而每个线程都持有this对象的对象锁,这不能实现线程的同步。 Java多线程代码如下 ; MyThread implements 3.{ int threadId; MyThread(int id)6.{ = id;8.} synchronized void run()11.{ (int i = 0; i < 100; ++i)13.{(Thread ID: + + : + i);15.}16.}17.} class ThreadDemo19.{20./**21.* @param args22.* @throws InterruptedException23.*/ static void main(String[] args) throws InterruptedException25.{ (int i = 0; i < 10; ++i)27.{ Thread(new MyThread(i))();(1);30.}31.}32.}以上就是对Java多线程的详细代码介绍。
多线程实现的四种方式
多线程实现的四种方式Thread裸线程、Executor服务、ForkJoin框架、Actor模型。
1、Thread裸线程
线程是并发最基本的单元。Java线程本质上被映射到操作系统线程,并且每个线程对象对应着一个计算机底层线程。每个线程有自己的栈空间,它占用了JVM进程空间的指定一部分。
线程的接口相当简明,你只需要提供一个Runnable,调用start开始计算。没有现成的API来结束线程,你需要自己来实现。
优点是很接近并发计算的操作系统/硬件模型,并且这个模型非常简单。多个线程运行,通过共享内存通讯。最大劣势是,开发者很容易过分的关注线程的数量。
线程是很昂贵的对象,创建它们需要耗费大量的内存和时间。这是一个矛盾,线程太少,你不能获得良好的并发性;线程太多,将很可能导致内存问题,调度也变得更复杂。如果你需要一个快速和简单的解决方案,你绝对可以使用这个方法,不要犹豫。
2、Executor服务
另一个选择是使用API来管理一组线程。幸运的是,JVM为我们提供了这样的功能,就是Executor接口。它隐藏了如何处理Runnable的细节。
它仅仅说,“开发者!给我任务,我会处理它!”更酷的是,Executors类提供了一组方法,能够创建拥有完善配置的线程池和executor。我们将使用newFixedThreadPool,它创建预定义数量的线程,并且不允许线程数量超过这个预定义值。
这意味着,如果所有的线程都被使用的话,提交的命令将会被放到一个队列中等待;当然这是由executor来管理的。在它的上层,有ExecutorService管理executor的生命周期,以及CompletionService会抽象掉更多细节,作为已完成任务的队列。
如果你需要精确的控制程序产生的线程数量,以及它们的精确行为,那么executor和executor服务将是正确的选择。例如,需要仔细考虑的一个重要问题是,当所有线程都在忙于做其他事情时,需要什么样的策略?
增加线程数量或者不做数量限制?把任务放入到队列等待?如果队列也满了呢?无限制的增加队列大小?
感谢JDK,已经有很多配置项回答了这些问题,并且有着直观的名字,例如上面的(4)。
线程和服务的生命周期也可以通过选项来配置,使资源可以在恰当的时间关闭。唯一的不便之处是,对新手来说,配置选项可以更简单和直观一些。然而,在并发编程方面,你几乎找不到更简单的了。总之,对于大型系统,使用executor最合适。
3、ForkJoin框架
通过并行流,使用ForkJoinPool(FJP),Java8中加入了并行流,从此我们有了一个并行处理集合的简单方法。它和lambda一起,构成了并发计算的一个强大工具。
如果你打算运用这种方法,那么有几点需要注意。首先,你必须掌握一些函数编程的概念,它实际上更有优势。其次,你很难知道并行流实际上是否使用了超过一个线程,这要由流的具体实现来决定。如果你无法控制流的数据源,你就无法确定它做了什么。
另外,你需要记住,默认情况下是通过实现并行的。这个通用池由JVM来管理,并且被JVM进程内的所有线程共享。这简化了配置项,因此你不用担心。
ForkJoin是一个很好的框架,当需要写一个包含并行处理的小型程序时,它是第一选择。它最大的缺点是,你必须预见到它可能产生的并发症。如果对JVM没有整体上的深入了解,这很难做到。这只能来自于经验。
4、Actor模型
JDK中没有actor的实现;因此你必须引用一些实现了actor的库。
简短地说,在actor模型中,你把一切都看做是一个actor。一个actor是一个计算实体,就像上面第一个例子中的线程,它可以从其他actor那里接收消息,因为一切都是actor。
在应答消息时,它可以给其他actor发送消息,或者创建新的actor并与之交互,或者只改变自己的内部状态。相当简单,但这是一个非常强大的概念。生命周期和消息传递由你的框架来管理,你只需要指定计算单元是什么就可以了。
另外,actor模型强调避免全局状态,这会带来很多便利。你可以应用监督策略,例如免费重试,更简单的分布式系统设计,错误容忍度等等。Akka Actors有Java接口,是最流行的JVM Actor库之一。
实际上,它也有Scala接口,并且是Scala目前默认的actor库。Scala曾经在内部实现了actor。不少JVM语言都实现了actor,比如Future。这些说明了Actor模型已经被广泛接受,并被看做是对语言非常有价值的补充。
Akka actor在内部使用ForkJoin框架来处理工作。Actor模型的强大之处来自于Props对象的接口,通过接口我们可以为actor定义特定的选择模式,定制的邮箱地址等。结果系统也是可配置的,只包含了很少的活动件。
这是一个很好的迹象!使用Actor模型的一个劣势是,它要求你避免全局状态,因此你必须小心的设计你的应用程序,而这可能会使项目迁移变得很复杂。同时,它也有不少优点,因此学习一些新的范例和使用新的库是完全值得的。
可以看出Scala非常简单,它的并发线程你无需跟线程啊,锁啊,线程间通信,线程间协同等难题打交道。它把这些都封装起来了。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。