Java线程模型

 

什么是Java线程模型

   因为Java字节码运行在JVM中,而JVM运行在各个操作系统上,所以当JVM想要进行线程创建和回收的这种操作时,是必须要调用操作系统的相关接口,也就是说JVM线程与操作系统线程之间存在着某种映射关系。

  这两种不同维度的线程之间的规范和协议呢,就是线程模型。有人可能要问了那为什么需要这种线程模型?我们在开发时如果直接调用操作系统的接口来创建和回收线程不是更加直接吗,这个问题的答案呢其实很容易理解就像我们现在为什么不常用汇编语言来进行开发而是使用更加简单更加容易上手的高级语言一样,这是一种自下而上的抽象方式。JVM线程对不同操作系统的原生线程进行了高级抽象,可以使开发者一般情况下可以不用关注下层的细节,而只要专注上层的开发就行了。

  但是对于我们个人来说还是需要秉持知其然并知其所以然的态度,就要去理解这种抽象方式,这也有助于我们在将来自己进行一些设计的时候,能够调用前人的思想。

线程模型种类

   在说线程模型之前,我们先看看操作系统内核线程是怎样的。

     我们以Linux系统为例,在Linux系统中Linux线程KLT(Kernel Level Thread)又被称为轻量级进程LWP(Light Weight Process),那么到底这是线程还是进程呢?我们可以这么去理解,线程是抽象概念,因为Linux内核没有专门为线程定义数据结构和调度算法,所以Linux去实现线程的方式是轻量级进程,其实本质还是进程,只不过加了一个轻量级的修饰词。那么轻量级进程与进程之间的区别在哪呢?一个Linux进程拥有自己独立的地址空间,而一个轻量级进程没有自己独立的地址空间,只能共享同一个轻量级进程组下的地址空间。

    Linux进程和线程都是使用了clone系统调用,区别仅仅在于向clone函数传递的参数不同,不同的参数代表是否指定共享地址空间等资源。好了下面我们来看线程模型的种类。  

 

 线程模型主要有三类分别如下:

  1. 一对一:1:1

    

    即一个用户线程对应一个内核线程,内核负责每个线程的调度,可以调度到其他处理器上面。

    一对一这种线程模型就是用户线程与内核线程建立了一对一的关系,即一个用户线程对应一个内核线程,内核负责每个线程的调度。

  优点

         1)这种关系模型比较简单好用,可以解决大部分场景下的问题。

   缺点:

   1)用户线程的阻塞和唤醒会直接映射到内核线程,容易引起内核态和用户态的切换,这种频繁切换会降低性能。但是一些语言引入了CAS机制来避免一部分情况下的切换,比如Java语言就使用了AQS这种函数级别的锁来减少内核级别的锁,就能有效的提升性能。

   2)Linux系统内核能够创建的线程数量还是比较有限的,所以这在一定程度上会限制并发量。目前大部分主流的JVM上都是采用这种线程模型。

 

  2. 多对一:N:1

    

    多对一线程模型,又叫作用户级线程模型,即多个用户线程映射到同一个内核线程上,用户线程的创建、调度、同步的所有操作全部都是由用户空间的线程来完成的。 

  优点:

    1)能够提高并发量

    2)大部分调度操作都是在用户空间完成,可以有效提升性能。

  缺点:

    1)当一个用户线程进行了内核调用并且阻塞的时候,其实线程在阻塞的这段时间内都无法进行内核调用,这非常致命。Java语言早起版本就是使用了这种线程模型。

 

  3. 多对多:M:N

     

    多对多模型,又叫作两级线程模型,它充分吸收前面两种线程模型的优点且尽量规避它们的缺点。在此模型下用户线程与内核线程是多对多(M : N,通常M >= N)的映射模型。

  优点:  

    1)  兼具多对一模型的轻量。

    2)由于对应了多个内核线程,则一个用户线程阻塞时,其他用户线程仍然可以执行;

    3)由于对应了多个内核线程,则可以实现较完整的调度、优先级等;

     缺点:

    1)实现比较复杂,比如Go语言采用的GMP线程模型就是这种多对多的方式实现的,这也是为什么使用goroutine可以实现高并发的原因之一。目前Java的Loom项目也在这方面进行探索。

 

结语

  了解线程模型其实在对我们理解基于Java对象的悲观锁和基于AQS的乐观所都是有帮助的。

 

posted @ 2021-10-10 18:24  songguojun  阅读(1722)  评论(0编辑  收藏  举报