Java并发编程-进程和线程

一、进程和线程

1. 进程

什么是进程?

简单来说,进程就是程序的一次启动和执行。进程是操作系统中的一个概念,它代表正在运行的程序的实例。每个进程都有自己的内存空间代码和数据,以及其他操作系统资源,如文件和设备。进程之间是相互独立的,它们不能直接访问彼此的内存空间,但可以通过特定的机制进行通信。

什么是程序?

程序是一组指令的集合,用来完成特定任务或解决特定问题的一系列计算机代码。程序通常存储在文件中,包括可执行文件或源代码
文件。**

进程和程序有什么关系?

从使用者角度来说明,一个程序可以启动多次,那么就会对应多个进程,例如我们在使用浏览器的时候,可以启动很多个浏览器。

从开发者角度来说明,程序是开发人员编写的 源代码二进制文件但程序本身是静态的,它只是存在于磁盘或其他存储设备中,并不具有执行的能力。**进程 是 程序在计算机上运行的实例**

1.1. 进程的大致结构

进程一般,是由程序段,数据段,进程控制块三个部分组成

image-20240305074813076
  • **程序段:**一般也被称为代码段,也就是需要执行指令的集合
  • **数据段:**是进程的操作数据在内存中的位置,
  • **程序控制块(Program Control Block)PCB:**包含进程的描述信息和控制信息,是进程存在的唯一标志
    • PCB主要由四大部分组成
      • 进程描述信息:进程描述信息是操作系统用来跟踪和管理进程的基本数据结构。它包括了进程的标识符(PID,Process ID)、进程状态(运行、就绪、等待等)、进程所有者、进程的父进程和子进程关系等。这些信息使得操作系统能够有效地管理系统中的所有进程,并在需要时进行调度和资源分配。
      • 进程的调度信息:进程的调度信息包括了操作系统用来决定进程执行顺序的相关数据。这些信息通常包括了进程的优先级、调度策略(如先进先出、轮转调度等)、调度队列中的位置等。通过调度信息,操作系统可以决定哪个进程应该优先执行,以及在多任务环境中如何分配系统资源。
      • 进程的资源信息:进程的资源信息描述了进程所拥有的系统资源,包括了内存、文件、设备等。这些资源信息包括了进程分配的内存空间、打开的文件描述符、设备访问权限等。操作系统使用这些信息来管理和分配系统资源,以确保进程能够正常运行并访问所需的资源。
      • 进程的上下文:进程的上下文是指进程在执行过程中的状态和环境信息。它包括了进程的寄存器内容、程序计数器、堆栈指针以及其他与进程执行相关的状态信息。操作系统在进行进程切换或者处理中断时,需要保存和恢复进程的上下文信息,以确保进程能够从中断或者切换中恢复到正确的执行状态。保存和恢复进程上下文是操作系统进行有效进程调度和管理的关键步骤之一。

2.线程

2.1. 线程的基本原理

早期的操作系统确实没有线程的概念,只有进程。在早期的操作系统中,进程是最小的资源分配单位,每个进程拥有独立的地址空间和资源,通过操作系统的调度器进行调度和管理。后来随着计算机的发展,CPU的性能越来越高,为了提高CPU的利用率,进程内部演进并发调度的诉求,于是就发明了线程。

线程指的是,进程代码段的一次顺序的执行流程,线程是CPU调度的最小单位,一个进程可以有多个线程,各个线程之间共享进程的内存空间、系统资源

进程仍然是操作系统的资源的分配的最小单位

在Java程序中当你启动一个Java程序时,实际上会启动至少两个线程。

  1. 主线程(Main Thread):这是程序开始执行时默认启动的线程,它是程序的入口点。主线程负责执行 main() 方法中的代码,并且在 main() 方法执行完毕后,主线程也会随之结束。
  2. 虚拟机线程(JVM Thread):除了主线程之外,Java虚拟机还会启动其他一些线程,用于执行不同的任务,例如垃圾回收、JIT编译、线程调度等。这些线程通常由Java虚拟机自动管理,开发者不需要过多地关注它们。

2.2.线程基本结构

image-20240305074748089

线程的基本信息

  1. 线程ID:线程唯一标识,同一个进程内的线程的ID不会重复
  2. **线程名称:**线程名称主要用于标识和区分不同的线程,提高程序的可读性和调试性。在多线程程序中,通常会创建多个线程来执行不同的任务,每个线程可能负责不同的工作,有不同的执行逻辑和处理方式。主要是可以方便用户识别是哪一类线程,用户可以自定义线程名称,如果没有指定,那么系统会默认给线程分配一个名称。
  3. **线程的优先级:**表示线程的调度的优先级,优先级越高,那么被CPU执行的可能性就越大。需要注意,在Java中,虽然可以通过设置线程优先级来影响线程调度器的行为,但是最终的调度取决于底层操作系统和Java虚拟机的具体实现。Java线程优先级只是给线程调度器一个提示,告诉它哪些线程可能更重要或更紧急,但并不能保证线程会按照指定的优先级顺序执行。实际上,线程优先级在不同的操作系统和不同的Java虚拟机中可能会有不同的行为
  4. **线程状态:**当前线程的执行状态,为新建就绪阻塞运行结束,等状态中的一种
  5. **其他信息:**例如是否为守护线程等

*程序计数器

  1. 程序计数器非常重要,它记录者线程下一条指令的代码的内存的中的地址

下面的同过一个简单的程序,来看一下,Java中线程的基本信息

public static void main(String[] args) {// 创建一个线程 打印出线程所有的基本信息Thread thread = new Thread(() -> {logger.error("=============== 线程启动了!==================");}, "线程1");// 启动线程thread.start();logger.error("线程的id:{}",thread.getId());logger.error("线程的名称:{}",thread.getName());logger.error("线程的优先级:{}",thread.getPriority());logger.error("线程的状态:{}",thread.getState());logger.error("线程的线程组:{}",thread.getThreadGroup());logger.error("线程的是否是守护线程:{}",thread.isDaemon());logger.error("线程的是否是活跃的:{}",thread.isAlive());logger.error("线程的是否是中断的:{}",thread.isInterrupted());
}

image-20240305080419858

2.3.栈内存

栈内存(Stack Memory)是计算机内存中的一种重要的内存分配方式,它主要用于存储函数调用时的局部变量、函数参数、函数调用返回地址以及一些临时数据。栈内存的管理是由编译器自动完成的,它的特点是后进先出(LIFO,Last In First Out)的数据结构。

  1. 函数调用: 当一个函数被调用时,系统会为该函数创建一个称为栈帧(Stack Frame)的内存块,栈帧中存储了函数的参数、局部变量以及函数调用返回地址等信息。每次函数调用时,都会在栈上分配一个新的栈帧,函数执行完毕后,其对应的栈帧会被销毁,从而释放相应的内存空间。
  2. 局部变量: 在函数内部声明的变量通常都存储在栈上。这些变量在函数执行期间可见,当函数执行结束时,它们的内存空间也会被释放。因此,栈内存的生命周期与函数调用的生命周期密切相关。
  3. 函数参数: 函数的参数通常也会存储在栈上,它们在函数调用时被压入栈中,并在函数执行期间被访问和使用。
  4. 递归调用: 栈内存也被广泛用于实现递归算法。每当递归函数调用自身时,都会在栈上创建一个新的栈帧,用于存储该次函数调用的参数和局部变量。递归调用结束后,栈帧被销毁,从而释放相关的内存空间。
  5. 有限空间: 栈内存的大小通常是有限的,这意味着在一段时间内,你可以使用的栈内存空间是有限的。当递归调用层次过深或者局部变量过多时,可能会导致栈溢出(Stack Overflow)错误。
  6. 高效访问: 由于栈内存的实现方式简单,并且具有连续的内存地址,因此对于处理函数调用和局部变量访问非常高效。

2.4.栈帧

栈帧(Stack Frame)是在函数调用时在栈内存中创建的一个内存块,用于存储函数的参数、局部变量、返回地址以及其他与函数执行相关的信息。每次函数调用时,都会创建一个新的栈帧,函数执行结束后,该栈帧会被销毁,从而释放相应的内存空间。

以下是栈帧的详细介绍:

  1. 返回地址(Return Address): 栈帧中存储了函数调用后返回到调用点的地址。当函数执行完毕时,程序需要知道从哪里继续执行,因此返回地址被保存在栈帧中。一般来说,函数执行完毕后,会跳转到该地址继续执行。
  2. 函数参数: 栈帧中包含了函数调用时传递的参数。这些参数被压入栈中,以便函数内部可以访问和使用它们。在函数执行过程中,可以通过栈帧访问这些参数。
  3. 局部变量: 函数中声明的局部变量也存储在栈帧中。局部变量在函数执行期间可见,只在函数作用域内有效。当函数执行结束时,这些局部变量的内存空间会被释放。
  4. 返回值: 如果函数有返回值,通常会在栈帧中预留一部分空间来存储该返回值。当函数执行完毕并准备返回时,返回值会被放置到对应的位置,以便调用者可以获取到函数的返回结果。
  5. 保存的上下文信息: 在一些体系结构中,栈帧可能会存储一些额外的上下文信息,如寄存器的状态或者其他与函数执行相关的信息,以便函数执行完毕后可以恢复调用点的状态。
  6. 栈指针(Stack Pointer): 栈指针是一个特殊的寄存器,它指向当前栈顶的位置。在函数调用过程中,栈指针会随着栈帧的创建和销毁而移动。当函数调用结束后,栈指针会被调整以释放对应的栈帧空间。
  7. 栈帧的顺序和布局: 栈帧通常按照一定的布局顺序在栈上分配空间,常见的布局顺序包括参数、返回地址、局部变量等。不同的编程语言和体系结构可能对栈帧的布局有所不同。

栈帧(Stack Frame)是在函数调用时在栈内存中创建的一个内存块,用于存储函数的参数、局部变量、返回地址以及其他与函数执行相关的信息。每次函数调用时,都会创建一个新的栈帧,函数执行结束后,该栈帧会被销毁,从而释放相应的内存空间。以下是栈帧的详细介绍:

  1. 返回地址(Return Address): 栈帧中存储了函数调用后返回到调用点的地址。当函数执行完毕时,程序需要知道从哪里继续执行,因此返回地址被保存在栈帧中。一般来说,函数执行完毕后,会跳转到该地址继续执行。
  2. 函数参数: 栈帧中包含了函数调用时传递的参数。这些参数被压入栈中,以便函数内部可以访问和使用它们。在函数执行过程中,可以通过栈帧访问这些参数。
  3. 局部变量: 函数中声明的局部变量也存储在栈帧中。局部变量在函数执行期间可见,只在函数作用域内有效。当函数执行结束时,这些局部变量的内存空间会被释放。
  4. 返回值: 如果函数有返回值,通常会在栈帧中预留一部分空间来存储该返回值。当函数执行完毕并准备返回时,返回值会被放置到对应的位置,以便调用者可以获取到函数的返回结果。
  5. 保存的上下文信息: 在一些体系结构中,栈帧可能会存储一些额外的上下文信息,如寄存器的状态或者其他与函数执行相关的信息,以便函数执行完毕后可以恢复调用点的状态。
  6. 栈指针(Stack Pointer): 栈指针是一个特殊的寄存器,它指向当前栈顶的位置。在函数调用过程中,栈指针会随着栈帧的创建和销毁而移动。当函数调用结束后,栈指针会被调整以释放对应的栈帧空间。
  7. 栈帧的顺序和布局: 栈帧通常按照一定的布局顺序在栈上分配空间,常见的布局顺序包括参数、返回地址、局部变量等。不同的编程语言和体系结构可能对栈帧的布局有所不同。

通过一个案例来了解一下栈帧

这是基础函数调用 计算数值累加的方法,下面通过这个方法来具体了解一下栈帧

public class ThreadStackDemo {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());private static final ReentrantLock lock = new ReentrantLock();private static final Condition condition = lock.newCondition();public static void main(String[] args) throws InterruptedException {int a = 1;int b = 1;Res res = add(a, b);logger.error("res:{}", res.getData());}public static Res add(int a, int b) {Res res = new Res();int c = a + b;res.setData(c);logger.error("c:{}", c);return res;}
}@Getter @Setter
class Res{private int data;}

image-20240305211819419

image-20240305211914740

这段代码的执行原理:

  1. main() 方法执行:
    • 当程序开始执行时,JVM会在主线程的调用栈上创建一个栈帧用于执行 main() 方法。
    • main() 方法中,首先声明了两个整型变量 ab,它们被分配在 main() 方法的栈帧中。
    • 然后调用 add(a, b) 方法,这个方法调用会创建一个新的栈帧并压入调用栈。
  2. add() 方法执行:
    • add() 方法被调用时,JVM在调用栈上创建一个新的栈帧用于执行该方法。
    • add() 方法内部,声明了一个 Res 对象 res,它被分配在 add() 方法的栈帧中。
    • 接着声明了一个整型变量 c,也被分配在 add() 方法的栈帧中。
    • 计算 a + b,将结果赋值给 c
    • 调用 res.setData(c) 方法,这个方法调用会在当前栈帧中执行。
    • 最后返回 res 对象。
  3. 返回到 main() 方法:
    • add() 方法执行完毕后,将返回到 main() 方法。
    • main() 方法中,接收 add() 方法返回的 Res 对象,并将其赋值给 res
    • 使用 logger 输出结果。
  4. 栈帧的销毁:
    • main() 方法执行完毕后,主线程的栈帧被销毁。
    • 随着 main() 方法的结束,整个程序执行结束,JVM 会关闭。

2.5.线程的7种状态

在Java中,线程可以处于不同的状态,这些状态反映了线程在其生命周期中的不同阶段和行为。

  1. 新建状态(NEW): 当线程对象被创建但尚未启动时,线程处于新建状态。此时,线程对象被分配了内存,但尚未调用 start() 方法启动线程。在新建状态下,线程并不会占用系统资源。
  2. 就绪状态(RUNNABLE): 当线程处于就绪状态时,表示线程已经被创建并且已经调用了 start() 方法,但是还没有被分配到 CPU 时间片,即线程处于就绪队列中等待系统调度执行。处于就绪状态的线程可能随时被调度执行,取决于操作系统的调度算法。
  3. 运行状态(RUNNING): 当线程处于运行状态时,表示线程已经获得了CPU时间片,正在执行任务。处于运行状态的线程可能在任何时候被系统中断或者主动放弃CPU控制权。
  4. 阻塞状态(BLOCKED): 当线程被阻塞时,处于阻塞状态。线程可能因为多种原因进入阻塞状态,比如等待锁的释放、等待输入/输出完成、等待其他线程执行完毕等。当满足某个条件时,线程会从阻塞状态转移到就绪状态,等待再次被调度执行。
  5. 等待状态(WAITING): 当线程处于等待状态时,表示线程暂时停止执行,直到接收到通知或者被中断。线程可能调用了 Object.wait() 方法、Thread.join() 方法,或者调用了一些阻塞方法时会进入等待状态。
  6. 超时等待状态(TIMED_WAITING): 类似于等待状态,但是在指定的时间内会自动返回。线程可能因为调用了带有超时参数的 Thread.sleep()Object.wait(timeout) 方法,或者调用了带有超时参数的 Thread.join(timeout) 方法时会进入超时等待状态。
  7. 终止状态(TERMINATED): 当线程执行完毕或者因为异常退出时,处于终止状态。线程对象的生命周期结束,线程被销毁,不再执行任何任务。

image-20240305212107456

3.线程和进程的区别

线程和进程是操作系统中管理和调度的两个基本概念,它们之间有着明显的区别:

  1. 定义:
    • 进程是程序执行时的一个实例。它包含了程序代码、数据以及进程的执行环境。
    • 线程是进程中的一个执行单元,是程序执行流的最小单元,一个进程可以包含多个线程。
  2. 资源分配:
    • 进程是系统分配资源的基本单位。每个进程拥有独立的内存空间、文件句柄等资源。
    • 线程是进程内的资源共享单位。同一进程内的所有线程共享相同的内存空间和其他资源。
  3. 并发性:
    • 进程是独立运行的程序,不同进程之间相互独立,彼此不受影响。
    • 线程是进程内的执行流,同一进程内的多个线程共享进程的资源,可以同时执行并且可以共享数据。
  4. 开销:
    • 进程之间切换需要的开销较大,因为它们拥有独立的地址空间和资源。
    • 线程之间切换的开销相对较小,因为它们共享同一进程的地址空间和资源。
  5. 通信与同步:
    • 进程之间通信需要采用特定的通信机制,如管道、消息队列、共享内存等。
    • 线程之间通信可以直接读写共享数据,但需要注意同步问题,防止数据竞争和死锁。
  6. 稳定性:
    • 进程之间的错误不会相互影响,一个进程的崩溃不会影响其他进程的稳定性。
    • 线程之间共享同一进程的资源,一个线程的错误可能会导致整个进程崩溃。
  7. 创建和销毁:
    • 创建和销毁进程的开销较大,需要分配和释放大量的系统资源。
    • 创建和销毁线程的开销相对较小,因为它们共享进程的资源,只需分配和释放少量的内存空间即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/722991.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Git分布式管理-头歌实验远程版本库

Git的一大特点就是,能为不同系统下的开发者提供了一个协作开发的平台。而团队如果要基于Git进行协同开发,就必须依赖远程版本库。远程版本库允许,我们将本地版本库保存在远端服务器,而且,不同的开发者也是基于远程版本…

力扣hot100:560.和为K的子数组(前缀和+哈希表)

分析: 这个题目乍一看,数据大小用暴力解法大概率会超时,可能想用双指针,但是问题出现在 可能存在负数,也就是说即使是找到了一个答案,后面也可能存在负数和正数抵消,又是答案,因此不…

SpringBoot集成Logback

logback logback-core:其它两个模块的基础模块。logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。logback-access:访问模块与Servlet容器集成提供…

08-prometheus监控的告警通知-alertmanager组件工具

一、概述 prometheus通过规则文件对比抓取到的数据,来判断是否触发告警,我们通过配置告警的工具altermanager进行告警通知; 规则文件,写的就是,当我们获取到的PromeQL的值到达一个设置的规则后,触发告警&am…

刷题笔记day27-回溯算法3

39. 组合总和 var path []int var tmp []int var result [][]int// 还是需要去重复,题目中要求的是至少一个数字备选的数量不同。 // 所以需要剪枝操作,右边的要比左边的> func combinationSum(candidates []int, target int) [][]int {// 组合问题pa…

白皮书发布|超融合运行 K8s 的场景、功能与优势

目前,不少企业都使用虚拟化/超融合运行 Kubernetes 和容器化应用。一些用户可能会有疑惑:既然 Kubernetes 可以部署在裸金属上,使用虚拟化不是“多此一举”吗? 在电子书《IT 基础架构团队的 Kubernetes 管理:从入门到…

详细分析Vue中的$refs用法

目录 1. 基本知识2. Demo 1. 基本知识 在Vue.js中,$refs是一个特殊的属性,用于在组件内部直接访问子组件或者DOM元素 作用: 访问DOM元素: 直接访问模板中的DOM元素,以便执行DOM操作,如聚焦、改变样式等 访…

[极客大挑战 2020]Roamphp1-Welcome ---不会编程的崽

buuctf上的题难度适中。越到后边会越难&#xff0c;但也有例外 页面报错了。报错的原因可能有很多种猜想。所以有没有一种可能是故意这么设计的。先抓包吧 发现是GET请求。修改请求方法再试试呢&#xff1f; <?php error_reporting(0); if ($_SERVER[REQUEST_METHOD] ! P…

Android Studio开发(一) 构建项目

1、项目创建测试 1.1 前言 Android Studio 是由 Google 推出的官方集成开发环境&#xff08;IDE&#xff09;&#xff0c;专门用于开发 Android 应用程序。 基于 IntelliJ IDEA: Android Studio 是基于 JetBrains 的 IntelliJ IDEA 开发的&#xff0c;提供了丰富的功能和插件…

Python 全栈系列232 再次搭建RabbitMQ

说明 最近想重新上RabbitMQ&#xff0c;主要目的还是为了分布式任务调度。在Kafka和RabbitMQ两者犹豫了一下&#xff0c;还是觉得RabbitMQ好一些。 在20年的时候有搞过一阵子的RabbitMQ,看了下当时的几篇文章&#xff0c;觉得其实想法一直没变过。 Python - 装机系列24 消息…

常用“树”数据结构

哈夫曼树 在许多应用中&#xff0c;树中结点常常被赋予一个表示某种意义的数值&#xff0c;称为该结点的权。从树的根到任意结点的路径长度(经过的边数)与该结点上权值的乘积&#xff0c;称为该结点的带权路径长度。树中所有叶结点的带权路径长度之和称为该树的带权路径长度&am…

出现身份验证错误,无法连接到本地安全机构 顺利解决这个问题希望能帮助大家

出现身份验证错误&#xff0c;无法连接到本地安全机构&#xff0c;远程计算机&#xff1a;XX&#xff0c;这可能是由于密码过期&#xff0c;如果密码已过期请更新密码。 我们可以在系统属性中对远程进行设置&#xff0c;以解决远程桌面无法连接到本地安全机构这一问题。 步骤…

倒计时34天

L2-1 堆宝塔 - B107 2023级选拔春季开学测重现 (pintia.cn) #include<bits/stdc.h> using namespace std; //#define int long long const int N2e56; const int inf0x3f3f3f3f; const double piacos(-1.0); vector<int>ve1,ve2; vector<vector<int> >…

企业出海WAS安全自动化解决方案

随着企业出海的日益激烈&#xff0c;安全风险正在成为企业日益关注的问题之一&#xff0c;九河云携手AWS带来了使用Amazon WAF 与 Amazon Shield 的 CloudFront安全自动化。Aws WAF是一种web应用防火墙&#xff0c;可帮助保护客户的web应用程序或api免遭常规web漏洞的攻击。Aws…

【Hadoop大数据技术】——Hadoop概述与搭建环境(学习笔记)

&#x1f4d6; 前言&#xff1a;随着大数据时代的到来&#xff0c;大数据已经在金融、交通、物流等各个行业领域得到广泛应用。而Hadoop就是一个用于处理海量数据的框架&#xff0c;它既可以为海量数据提供可靠的存储&#xff1b;也可以为海量数据提供高效的处理。 目录 &#…

【数据结构】用栈实现队列

前言&#xff1a;本节博客分享了用栈实现队列效果的思路以及代码&#xff0c;有需要借鉴即可。 1.题目及链接 LINK 2.思路分析 如果要用栈实现队列&#xff0c;我们直到栈是先入后出的一个效果&#xff0c;所以我们可以用两个栈&#xff0c;这样逆转两次数不就是入栈之前数组…

SpringBoot约定大于配置

什么是约定大于配置 "约定大于配置"&#xff08;Convention Over Configuration&#xff09;是一种理念&#xff0c;旨在通过默认约定和规则来减少开发人员需要做的配置工作。在Spring Boot框架中&#xff0c;这一原则得到了充分应用&#xff0c;帮助开发者更快地构…

Blender和3ds Max哪个会是行业未来?

Blender和3ds Max都是很强大的三维建模和渲染软件&#xff0c;各有各的好处。选择哪个软件更好&#xff0c;要看你的需求、预算、技术水平以及行业趋势等因素。 Blender最大的优点是免费且开源&#xff0c;这对预算有限的个人和小团队来说很有吸引力。它有很多建模工具和功能&…

在电脑桌面打开任意应用程序的快捷键

首先为某个程序&#xff08;比如谷歌浏览器&#xff09;创建一个快捷方式&#xff0c; 其次右键快捷方式&#xff0c;找到属性一栏 单击快捷键三个字右边的方框&#xff08;里面有一个“无”&#xff09;&#xff0c;然后按下你所需要设置的快捷键

【强化学习的数学原理-赵世钰】课程笔记(七)时序差分方法

一.内容概述 第五节课蒙特卡洛&#xff08;Mento Carlo&#xff09;方法是全课程中第一次介绍 model-free 的方法&#xff0c;本节课的 Temporal-difference learning&#xff08;TD learning&#xff09;是我们要介绍的第二种 model-free 的方法。基于蒙特卡洛&#xff08;Me…