详解Java中的线程模型与线程调度

JAVA线程模型

线程的实现主要有3种方式:

  • 使用内核线程实现(1:1)
  • 使用用户线程实现(1:N)
  • 使用用户线程加轻量级进程实现(N:M)

使用内核线程实现(Kernel-Level Thread, KLT)(1:1)

内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程的切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。

程序一般不会直接去使用内核,而是去使用线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上的线程,由于每个轻量级进程都是由一个内核线程支持的,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程直接1:1的关系称为一对一线程模型。

优点:

实现简单

缺点:

  • 线程的各种操作都需要系统调度,需要在用户态和内核态中来回切换,系统调用的代价相对较高;
  • 其次,每个轻量级继承都需要一个内核线程来支持,因此轻量级进程要消耗一定的内核资源,一个系统支持的轻量级进程数量有限,如果系统出现大量线程,会对系统性能有影响;

使用用户线程实现 (1:N)

广义上来说,一个线程只要不是内核线程,就可以认为是用户线程(User Thread,UT),从这个角度来看,轻量级进程也就是用户线程,但是轻量级进程始终是和内核线程有关系的,效率终究会受到限制。

狭义上来说,用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知到线程存在的实现。用户线程的建立、同步、调度和销毁完全在用户态完成,不需要内核的帮助。这种线程不需要切换到内核态,所以可以是非常快速且低消耗的,也可以支持更大规模的线程数量。这种进程与用户线程之间1:N的关系称为一对多线程模型。

优点:

不需要系统内核线程的支持,用户线程的很多操作对内核来说都是透明的,不需要用户态和内核态的频繁切换,使线程的创建、调度、同步等非常快;

缺点:

  • 需要用户程序自己处理线程的所有操作,JAVA曾经使用过用户线程,最终放弃使用。
  • 由于多个用户线程对应到同一个内核线程,如果其中一个用户线程阻塞,那么该其他用户线程也无法执行;
  • 内核并不知道用户态有哪些线程,无法像内核线程一样实现较完整的调度、优先级等;

使用用户线程加轻量级进程实现 N:M)

将内核线程与用户线程一起使用的组合方式,为N:M的关系,这种就是多对多线程模型。

优点:

  • 兼具多对一模型的轻量;
  • 由于对应了多个内核线程,则一个用户线程阻塞时,其他用户线程仍然可以执行;
  • 由于对应了多个内核线程,则可以实现较完整的调度、优先级等;

缺点:

实现复杂
Go语言中的goroutine调度器就是采用的这种实现方案

JAVA线程的实现

在JDK1.2之前,JAVA线程是基于称为“绿色线程(Green Thread)”的用户线程来实现的,而在JDK1.2中,线程模型替换为基于操作系统原生线程模型来实现的。

在目前的JDK版本中,操作系统支持怎样的线程模型,很大程度上决定了JAVA虚拟机的线程是怎样映射的,在不同的平台上实现可能是不一样的,线程模型只对线程的并发规模和操作成本产生影响,对JAVA程序的编码和运行过程来说,差异都是透明的。

对于Sun JDK 来说,它的Windows版本和 Linux 版本都是使用的一对一线程模型来实现的,即一条线程就映射到一条轻量级进程中,因为 Windows 系统和 Linux 系统提供的线程模型都是一对一的。

JAVA线程调度

线程调度是说系统为线程分配处理器使用权的过程。主要的调度方式有两种:

1.协同式线程调度

线程的执行时间是由线程本身控制的,线程工作执行完成之后,需要主动通知系统切换到另一个线程上。Lua 语言中的“协同例程”就是这类实现。

好处:实现简单

缺点:线程执行时间不可控,可能会一直阻塞,导致系统崩溃

2.抢占式线程调度

每个线程由系统来分配执行时间,线程的切换不由线程本身来决定(在JAVA中,Thread.yield() 可以主动让出CPU使用权,但要获取,线程本身是没有任何办法的)

优点:线程的执行时间系统可控,不会有一个线程阻塞而导致整个进程阻塞

JAVA 使用的是抢占式调度方式

JAVA线程状态

JAVA 定义了5种线程状态,在任意一个时间点上,线程有且只能有一个状态;5个状态分别如下:

1.新建(New)
创建后尚未启动的线程所处的状态
2.运行(Runable)
Runable 包括了操作系统线程状态的Running 和 Ready,也就是处于此状态的 JAVA 线程可能正在运行,也有可能正在等待分配CPU时间片
3.无限期等待(Waiting)
处于这种状态的线程不会被分配CPU 时间片,需要等待被其他线程显式地唤醒。
以下方法会让线程进入到无限期等待:

  • 调用没有设置 timeout 参数的 Object.wait() 方法
  • 调用没有设置 timeout 参数的 Thread.join() 方法
  • 调用LockSuport.park() 方法

4.有限期等待(Timed Waiting)
处于这种状态的线程也不会被分配CPU 时间片,但是不需要其他线程显式地唤醒,而是等待一定时间后会由系统自动唤醒
以下方法会让线程进入到有限期等待:

  • Thread.sleep() 方法
  • 调用设置了 timeout 参数的 Object.wait() 方法
  • 调用设置了 timeout 参数的 Thread.join() 方法
  • 调用LockSuport.parkNanos(long nanos) 方法
  • 调用LockSuport.parkUntil(long deadline) 方法

5.阻塞(Blocked)
线程被阻塞了,“阻塞状态”与“等待状态”的区别是: 阻塞状态在等待获取一个排它锁;等待状态则是在等待一段时间,或者唤醒动作的发生。在使用了 synchronized 等同步语句时会进入这种状态。
6.结束(Terminated)
终止线程的线程状态,线程已完成执行。

以上就是详解Java中的线程模型与线程调度的详细内容,更多关于Java 线程模型与线程调度的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java线程调度之线程休眠用法分析

    本文实例分析了Java线程调度之线程休眠用法.分享给大家供大家参考.具体分析如下: Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率.   这里要明确的一点,不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制.   线程休眠的目的是使线程让出CPU的最简单的做法之一,线程休眠时候,会将CPU资源交给其他线程,以便能轮换执行,当休眠一定时间后,线程会苏醒,进入准备状态等待执行.   线程休眠的方法是Thread.sleep

  • RxJava2 线程调度的方法

    subscribeOn和observeOn负责线程切换,同时某些操作符也默认指定了线程. 我们这里不分析在线程中怎么执行的.只看如何切换到某个指定线程. subscribeOn Observable.subscribeOn()在方法内部生成了一个ObservableSubscribeOn对象. 主要看一下ObservableSubscribeOn的subscribeActual方法. @Override public void subscribeActual(final Observer<? s

  • Java线程模型缺陷

    Java 编程语言的线程模型可能是此语言中最薄弱的部分.它完全不适合实际复杂程序的要求,而且也完全不是面向对象的.本文建议对 Java 语言进行重大修改和补充,以解决这些问题. Java 语言的线程模型是此语言的一个最难另人满意的部分.尽管 Java 语言本身就支持线程编程是件好事,但是它对线程的语法和类包的支持太少,只能适用于极小型的应用环境. 关于 Java 线程编程的大多数书籍都长篇累牍地指出了 Java 线程模型的缺陷,并提供了解决这些问题的急救包(Band-Aid/邦迪创可贴)类库.我

  • java自定义线程模型处理方法分享

    看过我之前文章的园友可能知道我是做游戏开发,我的很多思路和出发点是按照游戏思路来处理的,所以和web的话可能会有冲突,不相符合. 来说说为啥我要自定义线程模型呢? 按照我做的mmorpg或者mmoarpg游戏划分,线程被划分为,主线程,全局同步线程,聊天线程,组队线程,地图线程,以及地图消息分发派送线程等: 一些列,都需要根据我的划分,以及数据流向做控制. 游戏服务器,主要要做的事情,肯定是接受玩家的 命令请求 -> 相应的操作 -> 返回结果: 在服务器端所有的消息都会注册到消息管理器里,然

  • 简单了解Java Netty Reactor三种线程模型

    1. Reactor三种线程模型 1.1. 单线程模型 Reactor单线程模型,指的是所有的IO操作都在同一个NIO线程上面完成,NIO线程的职责如下: 1)作为NIO服务端,接收客户端的TCP连接: 2)作为NIO客户端,向服务端发起TCP连接: 3)读取通信对端的请求或者应答消息: 4)向通信对端发送消息请求或者应答消息. Reactor单线程模型示意图如下所示: Reactor单线程模型 由于Reactor模式使用的是异步非阻塞IO,所有的IO操作都不会导致阻塞,理论上一个线程可以独立处

  • 详解Java中的线程模型与线程调度

    JAVA线程模型 线程的实现主要有3种方式: 使用内核线程实现(1:1) 使用用户线程实现(1:N) 使用用户线程加轻量级进程实现(N:M) 使用内核线程实现(Kernel-Level Thread, KLT)(1:1) 内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程的切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上. 程序一般不会直接去使用内核,而是去使用线程的一种高级接口--轻量级进程(Light Weight Process,LWP),轻量级

  • 详解Java中的线程池

    1.简介 使用线程池可以避免线程的频繁创建以及销毁. JAVA中提供的用于实现线程池的API: Executor.ExecutorService.AbstractExecutorService.ThreadPoolExecutor.ForkJoinPool都位于java.util.concurrent包下. *ThreadPoolExecutor.ForkJoinPool为线程池的实现类. 2.Executor public interface Executor { /** * 向线程池提交一个

  • 详解Java中的线程让步yield()与线程休眠sleep()方法

    线程让步: yield() yield()的作用是让步.它能让当前线程由"运行状态"进入到"就绪状态",从而让其它具有相同优先级的等待线程获取执行权:但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权:也有可能是当前线程又进入到"运行状态"继续运行! 示例: class ThreadA extends Thread{ public ThreadA(String name){ super(name); }

  • 详解java中的阻塞队列

    阻塞队列简介 阻塞队列(BlockingQueue)首先是一个支持先进先出的队列,与普通的队列完全相同: 其次是一个支持阻塞操作的队列,即: 当队列满时,会阻塞执行插入操作的线程,直到队列不满. 当队列为空时,会阻塞执行获取操作的线程,直到队列不为空. 阻塞队列用在多线程的场景下,因此阻塞队列使用了锁机制来保证同步,这里使用的可重入锁: 而对于阻塞与唤醒机制则有与锁绑定的Condition实现 应用场景:生产者消费者模式 java中的阻塞队列 java中的阻塞队列根据容量可以分为有界队列和无界队

  • 详解Java中CountDownLatch异步转同步工具类

    使用场景 由于公司业务需求,需要对接socket.MQTT等消息队列. 众所周知 socket 是双向通信,socket的回复是人为定义的,客户端推送消息给服务端,服务端的回复是两条线.无法像http请求有回复. 下发指令给硬件时,需要校验此次数据下发是否成功. 用户体验而言,点击按钮就要知道此次的下发成功或失败. 如上图模型, 第一种方案使用Tread.sleep 优点:占用资源小,放弃当前cpu资源 缺点: 回复速度快,休眠时间过长,仍然需要等待休眠结束才能返回,响应速度是固定的,无法及时响

  • 一文详解Java中的类加载机制

    目录 一.前言 二.类加载的时机 2.1 类加载过程 2.2 什么时候类初始化 2.3 被动引用不会初始化 三.类加载的过程 3.1 加载 3.2 验证 3.3 准备 3.4 解析 3.5 初始化 四.父类和子类初始化过程中的执行顺序 五.类加载器 5.1 类与类加载器 5.2 双亲委派模型 5.3 破坏双亲委派模型 六.Java模块化系统 一.前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程

  • 详解Java中多线程异常捕获Runnable的实现

    详解Java中多线程异常捕获Runnable的实现 1.背景: Java 多线程异常不向主线程抛,自己处理,外部捕获不了异常.所以要实现主线程对子线程异常的捕获. 2.工具: 实现Runnable接口的LayerInitTask类,ThreadException类,线程安全的Vector 3.思路: 向LayerInitTask中传入Vector,记录异常情况,外部遍历,判断,抛出异常. 4.代码: package step5.exception; import java.util.Vector

  • 详解Java中list,set,map的遍历与增强for循环

    详解Java中list,set,map的遍历与增强for循环 Java集合类可分为三大块,分别是从Collection接口延伸出的List.Set和以键值对形式作存储的Map类型集合. 关于增强for循环,需要注意的是,使用增强for循环无法访问数组下标值,对于集合的遍历其内部采用的也是Iterator的相关方法.如果只做简单遍历读取,增强for循环确实减轻不少的代码量. 集合概念: 1.作用:用于存放对象 2.相当于一个容器,里面包含着一组对象,其中的每个对象作为集合的一个元素出现 3.jav

  • 详解Java中的sleep()和wait()的区别

    详解Java中的sleep()和wait()的区别 对于sleep()方法,我们首先要知道该方法是属于Thread类中的.而wait()方法,则是属于Object类中的. sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态. 在调用sleep()方法的过程中,线程不会释放对象锁. 而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象

  • 详解Java中AbstractMap抽象类

    jdk1.8.0_144 下载地址:http://www.jb51.net/softs/551512.html AbstractMap抽象类实现了一些简单且通用的方法,本身并不难.但在这个抽象类中有两个方法非常值得关注,keySet和values方法源码的实现可以说是教科书式的典范. 抽象类通常作为一种骨架实现,为各自子类实现公共的方法.上一篇我们讲解了Map接口,此篇对AbstractMap抽象类进行剖析研究. Java中Map类型的数据结构有相当多,AbstractMap作为它们的骨架实现实

随机推荐