Java并发编程-进程和线程

news2024/11/24 9:59:13

一、进程和线程

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.coloradmin.cn/o/1493723.html

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

相关文章

数据资产:隐藏在企业内部的金矿

引言 在这个数字化快速发展的时代,数据已成为企业的核心竞争力之一。数据资产不仅代表了企业的知识和智慧,还隐藏着巨大的商业价值。然而,许多企业对于数据资产的价值认识仍然不足,导致大量数据资源被埋没。本文将带您深入了解数…

游戏引擎渲染流程

一、渲染概述 我们首先看到渲染技术的发展 游戏渲染面临的挑战: 一个容器中同一时刻有大量的游戏对象需要进行渲染,并且不同对象渲染的形式、算法还有所差异,这些使得游戏的绘制系统变得非常复杂;其次,游戏引擎的渲染…

小白跟做江科大51单片机之DS1302可调时钟

原理部分 1.DS1302可调时钟介绍 单片机定时器主要占用CPU时间,掉电不能继续运行 图1 2.原理 图2 内部有寄存器,寄存的时候以时分秒寄存,以通信协议实现数据交互,就可以实现对数据进行访问和读写 3.主要寄存器定义 CE芯片使能…

去极值滑动平均值滤波FB(含线速度对比测试波形)

博途PLC滑动平均值滤波相关算法内容在专栏有系列文章,大家可以查看相关文章,常用链接如下: 1、滑动平均值滤波 https://rxxw-control.blog.csdn.net/article/details/124851884https://rxxw-control.blog.csdn.net/article/details/1248518842、博途PLC指数加权平均值滤波…

MATLAB环境下基于离散小波变换的体外血管图像处理

下面简要介绍小波变换的部分应用。 信号去噪。小波去噪是根据有效信号和噪声信号在小波变换后表现出的不同特性实现的,一般可用于去除语音、图像、视频等中的噪声信号。小波去噪方法根据对小波系数的非线性处理方式分为三类,分别是小波变换模极大值去噪…

数学建模【聚类模型】

一、聚类模型简介 “物以类聚, 人以群分”,所谓的聚类,就是将样本划分为由类似的对象组成的多个类的过程。聚类后,我们可以更加准确的在每个类中单独使用统计模型进行估计、分析或预测,也可以探究不同类之间的相关性和…

如何在控制台重新发送请求、修改请求参数

场景一:重新请求接口 - 鼠标右键点击请求,选择重放XHR - 可以看到重新发起了一次请求 注意:重放XHR不会重新渲染页面数据,只是单纯的请求接口 场景二:修改接口参数 - 右键鼠标右键点击接口、选择复制、选择以fetc…

【教程】无法验证app需要互联网连接以验证是否信任开发者

摘要 本文将探讨在使用苹果App时遇到无法验证开发者的情况,以及用户可以采取的解决方案。通过检查网络连接、重新操作、验证描述文件等方式来解决无法验证开发者的问题。同时,还介绍了开发者信任设置的步骤,以及使用appuploader工具进行安装…

Docker 部署Harbor 443端口冲突

如果Harbor的443端口和主机服务器的443端口存在冲突,那么需要修改Harbor的443 修改docker-compose中443端口,那么需要docker-compose.yml和harbor.yml保持一致配置 当修改harbor.yml重启之后不生效的,则需要进入harbor安装路径 执行 ./install.sh 命令 harbor.yml docker-…

TinyEMU编译与使用

TinyEMU编译与使用 1 介绍2 准备工作3 编译TinyEMU3.1 安装依赖库3.2 编译 4 运行TinyEMU4.1 在线运行4.2 离线运行 5 共享目录5.1 修改root_9p-riscv64.cfg5.2 启动TinyEMU5.3 执行挂载命令 6 TinyEMU命令帮助 1 介绍 原名为riscvemu,于2018-09-23,改为…

Git 进阶 高级用法,重要命令记录

本篇文章用于记录Git高级用法,新手可以看我的另一篇文章:Git基础教学。 Git git fetch 是git pull 的细分步骤,git pull 包含了git fetch git pull origin master 上述命令其实相当于git fetch git merge 在实际使用中,git fetc…

msvcr120.dll丢失怎样修复,更了解msvcr120.dll文件从而解决丢失问题

在电脑使用过程中,"msvcr120.dll丢失"是常见的错误提示之一。那么,今天就和大家聊聊msvcr120.dll文件,如果msvcr120.dll文件丢失的问题时什么原因导致的,让我们仔细来看一下msvcr120.dll是什么以及msvcr120.dll丢失怎样…

【二】【SQL Server】如何运用SQL Server中查询设计器通关数据库期末查询大题

教学管理系统201703153 教学管理系统数据库展示 成绩表展示 课程表展示 学生表展示 院系表展示 一、基本操作 设置复合主键 设置其他表的主键 设置字段取值范围 二、简单操作 第一题 第二题 第三题 第四题 结尾 最后,感谢您阅读我的文章,希望这些内容能…

2024年最受欢迎的15款缺陷管理工具

本文整理分享了15款再2024年受欢迎的bug(缺陷)跟踪管理工具:1.PingCode;2.Worktile;3.SpiraTeam;4. Jira Software;5. BugHerd;6. Zoho Projects;7.SmartSheet&#xff1…

linux实现远程文件夹共享-samba

目录 问题描述Samba如何挂载常用参数临时挂载实例一种长期挂载方法(已失败,仅供参考)查看挂载取消挂载umount失败 问题描述 我的代码需要访问存在于两个系统(win和linux)的文件夹,我不是文件夹的创建者&am…

【大数据】-- 创建 Paimon 外部表

如今,在数据湖三剑客(delta lake、hudi、iceberg)之上,又新出一派: apache paimon。我们恰好在工作中遇到,以下介绍在 dataworks 上,使用 maxcompute odps sql 创建 apache paimon 外部表的一些…

不可不知!AI大模型的力量超乎你的想象!

AI大模型是通过深度学习算法和人工神经网络训练出的具有庞大规模参数的人工智能模型。这些模型使用大量的多媒体数据资源作为输入,并通过复杂的数学运算和优化算法来完成大规模的训练,以学习和理解到输入数据的模式和特征。 想象一下,在一个…

【C++】string 类

1. 标准库中的string类 注意: 1. string是表示字符串的字符串类 2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。 比特就业课 3. string在底层实际是:basic_string模板类的别名,typedef b…

知识图谱与LLMs:微调 VS RAG、LLM的局限性以及如何克服

原文地址:Knowledge Graphs & LLMs: Fine-Tuning Vs. Retrieval-Augmented Generation 2023 年 6 月 6 日 GitHub:https://github.com/neo4j/NaLLM 大型语言模型 (LLM) 的第一波炒作来自 ChatGPT 和类似的基于网络的聊天机器人,相信在…

国货当自强,亿道3款手持新品同步上市

深圳市亿道信息股份有限公司是国内知名的加固型移动计算机提供商,同时也是国内较大的行业定制及终端产品方案公司之一。 过去我们一直专注于平板电脑集成RFID射频识别、电子标签技术的研发生产与销售,以成熟的平板电脑制造技术,为客户提供RF…