线程

2022/09/06 java高并发 共 3722 字,约 11 分钟
闷骚的程序员

1. 什么是线程?

实际上线程,本身不仅仅是java应用语言的概念,它的概念最早是属于操作系统的。

  • 线程本身是操作系统执行调度的基本单元,它本身是一块可以由操作系统(CPU)进行调度的内存空间。
  • 线程既然是操作系统调度的执行单元,它就会拥有自己的执行栈、程序计数器和一些相关的线程上下文信息等。
  • 线程是操作系统级别进行调度的实体,一个线程对象的生命周期,是由操作系统进行管理的。
  • 线程表示操作系统调度程序的一条独立路径,它可以独立执行,也可以与其他线程并发执行。通俗地说,线程就像是程序中的一条独立的执行流程,可以在同一时间内执行多个任务,使得程序能够同时完成多项操作。

操作系统的线程调度,反应到java中,本质上是一个java对象。既然是对象,意味着它需要占用内存,它可以保存内容等。

2. 操作系统的线程和JVM中的线程存在什么关系?

在操作系统层面,JVM调度的线程实际上对应于操作系统中的原生线程(Native Thread)或者轻量级进程(Lightweight Process),具体取决于操作系统的实现方式。

  • 原生线程(Native Thread):在大多数现代操作系统中,线程的创建和管理是由操作系统的内核负责的。JVM会向操作系统请求创建一个原生线程,操作系统会分配相应的资源(如内存空间、CPU时间片等)给这个线程,并在调度器中注册该线程。操作系统会根据调度器的调度策略来决定哪个线程应该被执行,以及何时执行。因此,JVM调度的线程在操作系统层面上实际上是由操作系统的调度器来进行调度和管理的。
  • 轻量级进程(Lightweight Process):有些操作系统提供了轻量级进程的概念,比如在Linux中就有类似于线程的轻量级进程(pthread)。轻量级进程是由操作系统内核管理的,但相比于原生线程,它具有更轻量级的特性,例如更少的资源消耗和更快的创建速度。JVM可能会利用操作系统提供的轻量级进程机制来实现Java线程的调度。

总之,在操作系统层面,JVM调度的线程实际上是由操作系统的调度器来进行调度和管理的,JVM只是向操作系统请求创建线程,并提供线程的执行代码。操作系统负责分配资源和决定线程的执行顺序。

3. 操作系统的调度器

操作系统的调度器是操作系统内核中的一个重要组件,负责管理和调度系统中的各种资源(如CPU、内存等),以便有效地执行多个任务,并满足用户和程序的需求。它的主要职责包括:

  • 进程调度:管理系统中的进程(或轻量级进程、线程),决定哪个进程可以获得CPU的执行时间。调度器会根据一定的调度算法,如先来先服务(FCFS)、最短作业优先(SJF)、优先级调度、时间片轮转等,来确定下一个要执行的进程。

  • 资源分配:调度器负责分配系统资源,例如CPU时间片、内存等,给各个进程或线程,以便它们能够顺利执行。

  • 处理中断:调度器会处理系统中发生的各种中断事件,包括时钟中断、I/O中断等,以便适时地调度执行下一个任务。

  • 实时调度:对于实时系统,调度器还需要满足任务对响应时间的特定要求,确保实时任务能够在规定的时间内完成。

  • 动态调整:根据系统负载和各个任务的优先级等情况,调度器可能会动态地调整调度策略,以达到最佳的系统性能。

在多任务操作系统中,调度器是一个核心组件,它的性能和效率直接影响到系统的整体表现和用户体验。一个优秀的调度器应该能够合理地分配系统资源,保证任务能够按时完成,并尽可能地降低系统的响应时间和延迟。

4. 深入理解java线程

结论:java中的线程,本质上是一个java对象,由JVM负责创建、管理,并注册到操作系统的调度器上由操作系统进行调度

在Java中,线程是程序执行的基本单元,它负责执行代码,并且可以独立运行。线程本质上是操作系统调度的执行单元,它拥有自己的执行栈、程序计数器和一些相关的线程上下文信息。

线程和普通对象的不同之处在于,线程是操作系统级别的实体,而不仅仅是Java虚拟机中的一个对象。线程具有自己的生命周期,包括创建、就绪、运行、阻塞和销毁等阶段。线程可以并发执行,使得程序能够同时执行多个任务,从而提高了程序的性能和响应能力。

Java中的线程是通过Thread类来表示的,通过创建Thread对象并调用start()方法来启动线程。线程在运行时会执行指定的run()方法中的代码,run()方法定义了线程要执行的任务。

线程驱动整个代码执行链路往下运转的过程是通过操作系统的调度机制实现的。当一个线程执行完毕或者被阻塞时,操作系统会根据一定的调度策略选择另一个就绪状态的线程来执行。这种调度方式使得多个线程能够交替执行,从而实现了并发执行的效果。

在Java中,线程(Thread)是指程序中的一条执行路径,它可以独立执行,同时也可以与其他线程并发执行。

  1. 线程的创建:线程可以由Java程序员通过创建Thread类的实例来创建。Java提供了Thread类来支持线程的创建,您可以通过扩展Thread类或者实现Runnable接口,并创建Thread类的实例来创建线程。线程的创建包括了为线程分配内存空间、初始化线程的栈空间等操作。

  2. 线程的启动:一旦创建了线程的实例,它并不会立即开始执行,而是需要调用start()方法来启动线程。start()方法会告诉JVM,这个线程已经准备好了,可以执行了。JVM会在合适的时候调度这个线程,并且开始执行线程的run()方法。调用start()方法之后,线程会被添加到JVM的线程调度器中,等待执行。

在JVM中,线程调度器负责根据一定的调度算法来决定哪个线程应该被执行,以及何时执行。JVM会根据线程的优先级、状态等信息来进行线程的调度。一旦线程被调度到执行,它就会独立地在JVM中运行,直到它完成了任务或者被中断。

因此,可以说线程实际上是由JVM进行调度和管理的,程序员通过创建线程对象并调用start()方法来告诉JVM需要创建和启动线程,而JVM负责实际的线程调度和管理工作。

举个简单的例子,假设你有一个程序,其中有一个线程负责接收用户输入,另一个线程负责在后台进行数据处理,还有一个线程负责更新UI界面。这三个线程可以并发地执行,互不干扰,使得程序在等待用户输入的同时能够进行数据处理和更新UI界面,提高了程序的响应速度和效率。

从代码的角度来说,线程的本质是一段可以独立执行的代码块,可以通过创建Thread类的实例并调用其start()方法来创建并启动一个线程。线程可以运行任何有效的Java代码,通常在一个线程中执行的是一个方法或者一段代码块。在Java中,线程是通过操作系统的线程机制实现的,每个线程都有自己的执行栈和执行路径,可以独立地执行任务。

在Java中,线程实际上是由JVM(Java虚拟机)进行调度和管理的。

JVM负责创建、管理、调度线程对象,它实际上是在创建完线程对象后将其注册到操作系统的调度器上去,底层的调用实际上是由操作系统的调度器负责调度的。

5. 线程上下文切换

线程上下文切换指的是,当操作系统需要将 CPU 从一个线程切换到另一个线程时,为了保留和恢复线程的执行状态而进行的一系列操作。这种切换会对性能产生一定的影响。以下是通俗易懂的案例来解释这一点:

场景:排队办理业务 假设你在银行办理业务,银行窗口(CPU)一次只能服务一个人(线程)。以下是几种情况:

1. 一个人独占窗口(没有上下文切换)

如果只有一个人,他可以一直在窗口办理自己的事情,不需要来回切换。效率最高。

2. 多人排队且依次服务(少量上下文切换)

如果有很多人排队,每次轮到一个人,窗口开始为他服务,直到办完为止再换下一个人。这时有轻微的“切换时间”,比如:前一人离开,下一人走到窗口,这种切换时间可以忽略不计。

3. 频繁打断(频繁上下文切换)

如果银行突然规定,每个人只能办理10秒业务,然后暂停,轮到下一个人,10秒后再切回来继续。每个人都需要不停地轮流上场,离开时还需要告诉银行工作人员:

  • 自己办到哪里了(保存状态)。
  • 回来时重新告诉工作人员:之前办到了哪一步(恢复状态)。

这样一来,原本简单的业务变得复杂,效率也明显下降,因为大量时间被浪费在“保存状态”和“恢复状态”上,而不是用来实际办理业务。

线程上下文切换的性能影响 计算机的线程切换就像银行的这种“排队和打断”。在上下文切换中,操作系统需要:

  1. 保存当前线程的寄存器、程序计数器等信息。
  2. 切换到另一个线程,并加载它的寄存器和状态。

这些操作虽然很快(通常只需几微秒到几毫秒),但如果切换过于频繁,CPU的时间就被浪费在切换操作上,而不是实际计算或处理任务。

举个实际例子 假设你正在开发一个聊天应用,每次用户发送消息,后台需要:

  1. 验证用户身份。
  2. 存储消息到数据库。
  3. 通知接收方。

如果你启动了数百个线程来处理这些任务,而每个线程处理一点点后就被切换,系统可能会因为频繁切换而变得缓慢,甚至出现卡顿。

而如果你使用更少的线程(比如线程池)或异步处理,则可以减少切换,提升整体效率。

总结 线程上下文切换的性能影响来源于“保存和恢复状态的开销”。当切换频率较低时,这种开销可以忽略;但当线程数量过多或频繁切换时,性能会显著下降。优化线程的数量和调度方式是解决问题的关键。

文档信息

Search

    Table of Contents