首页 > 编程语言 >java线程核心原理

java线程核心原理

时间:2024-01-19 12:22:23浏览次数:30  
标签:状态 优先级 java 操作系统 Thread 线程 原理 CPU

1. 线程的调度与时间片

1.1 java线程与操作系统

  • 现代操作系统(如Windows、Linux、Solaris)提供了强大的线程管理能力,Java不需要再进行自己独立的线程管理和调度,而是将线程调度工作委托给操作系统的调度进程去完成。在某些系统(比如Solaris操作系统)上,JVM甚至将每个Java线程一对一地对应到操作系统的本地线程,彻底将线程调度委托给操作系统

1.2 CPU时间片

  • 由于CPU的计算频率非常高,每秒计算数十亿次,因此可以将CPU的时间从毫秒的维度进行分段,每一小段叫作一个CPU时间片。

  • 对于不同的操作系统、不同的CPU,线程的CPU时间片长度都不同。假定操作系统(比如Windows XP)线程的时间片长度为20毫秒,在一个2GHz的CPU上,一个时间片可以进行计算的次数是20亿/(1000/20)=4000万次,也就是说,一个时间片内的计算量是非常巨大的。

  • 目前操作系统中主流的线程调度方式是: 基于CPU时间片方式进行线程调度

  • 线程只有得到CPU时间片才能执行指令,处于执行状态,没有得到时间片的线程处于就绪状态,等待系统分配下一个CPU时间片。

  • 由于时间片非常短,在各个线程之间快速地切换,因此表现出来的特征是很多个线程在同时执行或者并发执行

  • 线程的调度模型目前主要分为两种:分时调度模型抢占式调度模型

    1. 分时调度模型: 系统平均分配CPU的时间片,所有线程轮流占用CPU。分时调度模型在时间片调度的分配上,所有线程人人平等

image

三个线程轮流得到CPU时间片,一个线程执行时,另外两个线程处于就绪状态。

  1. 抢占式调度模型:系统按照线程优先级分配CPU时间片。优先级高的线程,优先分配CPU时间片,如果所有就绪线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些

由于目前大部分操作系统都是使用抢占式调度模型进行线程调度,Java的线程管理和调度是委托给操作系统完成的,所以,Java的线程调度也是使用抢占式调度模型,因此Java的线程都有优先级。

2. 线程的优先级

  • 在Thread类中有一个实例属性两个实例方法,专门用于进行线程优先级相关的操作,与线程优先级相关的成员属性为:

    private int priority; //该属性保存一个Thread实例的优先级,即1~10之间的值
    
  • 与Thread类线程优先级相关的实例方法为:

    public final int getPriority() // 获取线程优先级。
    public final void setPriority(int priority) // 设置线程优先级。
    

    Thread实例的priority属性默认是级别5,对应的类常量是NORM_PRIORITY

    优先级最大值为10,最小值为1,Thread类中定义的三个优先级常量如下:

    public static final int MIN_PRIORITY=1;
    public static final int NORM_PRIORITY=5;
    public static final int MAX_PRIORITY=10;
    

    Java中使用抢占式调度模型进行线程调度。priority实例属性的优先级越高,线程获得CPU时间片的机会越多,但也不是绝对的。

    public class PriorityDemo {
        public static final int SLEEP_GAP = 1000;
        
        static class PrioritySetThread extends Thread {
            static int threadNo = 1;
           
            public PrioritySetThread() {
                super("thread-" + threadNo);
                threadNo++;
            }
     
            public long opportunities = 0;
            
            public void run() {
                for (int i = 0; ; i++) {
                    opportunities++;
                }
            }
        }
        
        public static void main(String args[]) throws InterruptedException {
            
            PrioritySetThread[] threads = new PrioritySetThread[10];
            for (int i = 0; i < threads.length; i++) {
                threads[i] = new PrioritySetThread();
                //优先级的设置,从1-10
                threads[i].setPriority(i + 1);
            }
            
            for (int i = 0; i < threads.length; i++) {
                threads[i].start();
                
            }
            
            Thread.sleep(SLEEP_GAP);
            
            for (int i = 0; i < threads.length; i++) {
                threads[i].stop();
            }
            
            for (int i = 0; i < threads.length; i++) {
                  System.out.println(threads[i].getName() +
                        ";优先级为-" + threads[i].getPriority() +
                        ";机会值为-" + threads[i].opportunities
                );
            } 
        }
    }
    

image

10个线程中某个线程的实例属性opportunities的值越大,就表明该线程获得的CPU时间片越多。

  1. 高优先级的线程获得的执行机会更多。可以看到: 优先级在6级以上的线程执行机会明显偏多,整体对比非常明显。
  2. 执行机会的获取具有随机性,优先级高的不一定获得的机会多。比如,例子中的thread-10比thread-9优先级高,但是thread-10所获得的机会反而偏少。

3. 线程声明周期

  • Java中的线程的生命周期分为6种状态。

  • Thread类有一个实例属性一个实例方法专门用于保存和获取线程的状态。

    • 用于保存线程Thread实例状态的实例属性为:
    private int threadStatus; // 以整数的形式保存线程的状态。
    
    • Thread类用于获取线程状态的实例方法为:
    public Thread.state getstate(); // 返回当前线程的执行状态,一个枚举类型值
    

    Thread.State是一个内部枚举类,定义了6个枚举常量,分别代表Java线程的6种状态,具体如下:

    public enum State {
            NEW, // 新建
            RUNNABLE,// 就绪、运行
            BLOCKED,// 阻塞
            WAITING, // 等待
            TIMED_WAITING, // 计时等待
            TERMINATED; // 结束
        }
    
    
    

在Thread.State 定义的6种状态中,有4种是比较常见的状态

  • NEW(新建)状态
  • RUNNABLE(可执行)状态
  • TERMINATED(终止)状态
  • TIMED_WAITING(限时等待)状态。

3.1 NEW 状态

  • Java源码对NEW状态的注释说明是: 创建成功但是没有调用start()方法启动的Thread线程实例都处于NEW状态。
  • Thread线程实例的调用start()方法之后,其状态就从NEW状态到RUNNABLE状态,但并不意味着线程立即就能获取CPU时间片并且立即执行,中间需要一系列的操作系统内部操作。

3.2 RUNNABLE 状态

  • 当调用了Thread实例start()方法后,下一步如果线程获取CPU时间片开始执行,JVM将异步调用线程的run()方法执行其业务代码。
  • run()方法被异步调用之前,JVM做了哪些事情呢?
    • JVM的幕后工作和操作系统的线程调度有关。Java中的线程管理是通过JNI本地调用的方式,委托操作系统的线程管理API完成的。
    • 当Java线程的Thread实例的start()方法被调用后,操作系统中的对应线程进入的并不是运行状态,而是就绪状态,而Java线程并没有这个就绪状态。操作系统中线程的就绪状态是什么状态的呢?JVM的线程状态与其幕后的操作系统线程状态之间的转换关系如图:

image

  • 一个操作系统线程如果处于就绪状态,表示该线程已经满足了执行条件,但是还不能执行。处于就绪状态的线程需要等待操作系统的调度,一旦就绪状态线程操作系统选中,获得CPU时间片,线程就开始占用CPU,开始执行线程的代码,这时线程的操作系统状态发生了改变,进入了运行状态

  • 在操作系统中,处于运行状态的线程在CPU时间片用完之后,又回到就绪状态,等待CPU的下一次调度。就这样,操作系统线程在就绪状态和执行状态之间被系统反复地调度,一直持续直到线程的代码逻辑执行完成或者异常终止。这时线程的操作系统状态又发生了改变,进入了线程的最后状态TERMINATED状态。

  • 就绪状态和运行状态都是操作系统中的线程状态。在Java语言中,并没有细分这两种状态,而是将这两种状态合并成同一种状态RUNNABLE状态。因此,在Thread.State枚举类中,没有定义线程的就绪状态和运行状态,只是定义了RUNNABLE状态。这就是Java线程状态和操作系统中的线程状态有所不同的地方。

    NEW状态的Thread实例调用了start()方法后,线程的状态将变成RUNNABLE状态。

    尽管如此,线程的run()方法不一定会马上被并发执行,需要在线程获取了CPU时间片之后,才会真正启动并发执行。

3.3 TERMINATED 状态

  • 处于RUNNABLE状态的线程在run()方法执行完成之后就变成终止状态TERMINATED了。
  • 如果在run()方法执行过程中发生了运行时异常而没有被捕获run()方法将被异常终止,线程也会变成TERMINATED状态。

3.4 TIMED_WAITING 限时等待状态

  • 线程处于一种特殊的等待状态,准确地说,线程处于限时等待状态。能让线程处于限时等待状态的操作大致有以下几种:
Thread.sleep(int n) // 使得当前线程进入限时等待状态,等待时间为n毫秒。
Object.wait() // 带时限的抢占对象的monitor锁。
Thread.join() // 带时限的线程合并。
LockSupport.parkNanos() // 让线程等待,时间以纳秒为单位。
LockSupport.parkUntil() // 让线程等待,时间可以灵活设置。

4. 使用 Jstack 工具查看线程状态

  • 有时,服务器CPU占用率会一直很高,甚至一直处于100%。如果CPU使用率居高不下,自然是有某些线程一直占用着CPU资源,如何査看CPU占用率较高的线程呢?或者说,如何查看到线程的状态呢?一种比较快捷的办法是使用Jstack工具。

  • Jstack工具是Java虚拟机自带的一种堆栈跟踪工具。Jstack用于生成或导出(DUMP)IVM虚拟机运行实例当前时刻的线程快照。线程快照是对当前JVM实例内每一个线程正在执行的方法堆栈的集合,生成或导出线程快照的主要目的是用于定位线程出现长时间运行、停顿或者阻塞的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。线程出现停顿的时候通过Jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
    Jstack命令的语法格式:

    jstack <pid> // pid表示Java进程id,可以用jps命令查看
    

image

  • 一般情况下,通过Jstack输出的线程信息主要包括: JVM线程用户线程等。

    • JVM线程会在JVM启动时就存在,主要用于执行譬如垃圾回收、低内存的检测等后台任务,这些线程往往在JVM初始化的时候就存在。
    • 用户线程则是在程序创建了新的线程时才会生成。这里要注意的是:
      1. 在实际运行中,往往一次DUMP的信息不足以确认问题。建议产生三次DUMP信息,如果每次DUMP都指向同一个问题,我们才确定问题的典型性。
      2. 不同的Java虚拟机的线程导出来的DUMP信息格式是不一样的,并且同一JVM的不同版本DUMP信息也有差别。

image

  • GC task thread为垃圾回收线程,此类该线程会负责进行垃圾回收。通常JVM会启动多个GC线程,在GC线程的名称中,#后面的数字会累加,如GC task thread#1GC task thread#2等。
  • VM Periodic Task Thread线程是JVM周期性任务调度的线程,该线程在JVM内使用得比较频繁,比如定期的内存监控、JVM运行状况监控。
  • Jstack指令所输出的信息中包含以下重要信息:
    1. tid: 线程实例在JVM进程中的id。
    2. nid: 线程实例在操作系统中对应的底层线程的线程id。
    3. prio:线程实例在JVM进程中的优先级。
    4. os prio:线程实例在操作系统中对应的底层线程的优先级。
    5. 线程状态:如runnablewaiting on condition等。

标签:状态,优先级,java,操作系统,Thread,线程,原理,CPU
From: https://www.cnblogs.com/ccblblog/p/17974361

相关文章

  • 深入理解JavaScript堆栈、事件循环、执行上下文、作用域以及闭包
    合集-JavaScript进阶系列(5) 1.JavaScriptthis绑定详解01-092.JavaScriptapply、call、bind函数详解01-093.JavaScriptforEach方法跳出循环01-024.深入理解JavaScript堆栈、事件循环、执行上下文和作用域以及闭包01-105.JavaScript到底应不应该加分号?JavaScript自......
  • java多态
    有两个类,一个Animal类,一个Cat类,其中Cat是Animal的子类,此时我在主函数中这样声明一个对象"Animalanimal=newCat();",此时animal实际上是Cat类此时,Animal类中没有catMouse()这个方法,Cat类中有这个方法,我在主函数声明了"Animalanimal=newCat();"后,无法调用animal.catchMouse();......
  • java相似度算法计算
     publicclassCompareStrSimUtil{privatestaticintcompare(Stringstr,Stringtarget,booleanisIgnore){intd[][];//矩阵intn=str.length();intm=target.length();inti;//遍历str的intj;//遍历......
  • HanLP — 汉字转拼音 -- JAVA
    目录语料库训练加载语料库训练模型保存模型加载模型计算调用HanLP在汉字转拼音时,可以解决多音字问题,显示输出声调,声母、韵母,通过训练语料库,本文代码为《自然语言处理入门》配套版本HanLP-1.7.5对重载不是重任进行转拼音,效果如下:原文:重载不是重任拼音(数字音调):chong2,zai3,bu......
  • docker构建java镜像,运行镜像出现 no main manifest attribute, in /xxx.jar
    背景本文主要是一个随笔,记录一下出现"nomainmanifestattribute"的解决办法问题原因主要是近期在构建一个镜像,在镜像构建成功后,运行一直提示"nomainmanifestattribute",但是还在想,是不是Dockerfile写错了,后来仔细检查了一下,发现是在pom文件下build节点下配置问题,修改配置......
  • java创建线程的4种方式
    1.Thread类一个线程在Java中使用一个Thread实例来描述。Thread类是Java语言一个重要的基础类,位于java.lang包中。Thread类有不少非常重要的属性和方法,用于存储和操作线程的描述信息。1.1线程ID属性:privatelongtid,此属性用于保存线程的ID。这是一个private类型属性,外......
  • Java里ArrayList中的toArray()用法
    深入理解List的toArray()方法和toArray(T[]a)方法这两个方法都是将列表List中的元素转导出为数组,不同的是,toArray()方法导出的是Object类型数组,而toArray[T[]a]方法导出的是指定类型的数组。下面是两个方法的申明及说明,摘自Java8的API文档。toArray()方法的分析Object[]toA......
  • 基于JAVA的新闻类网站
    21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,管理工作的重要性已逐渐被人们所认识,科学化的管理,使信息存储达到准确、快速、完善,并能提高工作管理效率,促进其发展。论文主要是对新闻类网站进行了介绍,包括研......
  • 论文翻译 | 【深入挖掘Java技术】「底层原理专题」深入分析一下并发编程之父Doug Lea
    前提介绍DougLea在州立大学奥斯威戈分校(DougLea)摘要本文深入探讨了一个Java框架的设计、实现及其性能。该框架遵循并行编程的理念,通过递归方式将问题分解为多个子任务,并利用工作窃取技术进行并行处理。所有子任务完成后,其结果被整合以形成完整的并行程序。在总体设计上,该框架借鉴......
  • javascript中的undefined可以被重写
    众所周知,当声明一个变量,并且没有给赋值的情况下,它的初始值是 undefined。但是在javascript中,怎么检查一个值是否为 undefined 呢?在现代浏览器中,你可以安全的直接比较将变量是与 undefined 进行比较if(name===undefined){//...}一些人反对直接使用 undefined......