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. 创建和销毁:
    • 创建和销毁进程的开销较大,需要分配和释放大量的系统资源。
    • 创建和销毁线程的开销相对较小,因为它们共享进程的资源,只需分配和释放少量的内存空间即可。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法-希尔排序

引言 在计算机科学中,数据结构和算法是构建高效软件系统的基石。而排序算法作为算法领域的重要组成部分,一直在各种应用场景中发挥着关键作用。今天我们将聚焦于一种基于插入排序的改进版本——希尔排序(Shell Sort),深…

优思学院|质量和企业的盈利能力有何关系?

质量和企业的盈利能力有何关系?三十年前,这个问题就已经被提出。当时的学者们研究了高质量产品如何带来更高的盈利。虽然这听起来像是老生常谈,但它的真理至今仍深深影响着我们的商业决策。 为了更直观地理解,一些学者绘制了以下…

力扣大厂热门面试算法题 - 滑动窗口

最长和谐子序列、重复的DNA序列、找到字符串中所有字母异位词、滑动窗口最大值、最小区间。每题做详细思路梳理,配套Python&Java双语代码, 2024.03.06 可通过leetcode所有测试用例。 目录 594. 最长和谐子序列 解题思路 完整代码 Java Python …

亚信安慧AntDB:数据库自主创新的缩影

AntDB作为一款自主研发的数据库系统,具备了国产化升级改造的核心能力。这款数据库系统通过不懈努力和持续探索,实现了从跟随他人到引领潮流的华丽转身。AntDB不仅仅是一种技术产品,更是体现了自主研发能力的缩影,体现了科技企业在…

基于SSM的农业信息管理系统的设计与实现(有报告)。Javaee项目。ssm项目。

演示视频: 基于SSM的农业信息管理系统的设计与实现(有报告)。Javaee项目。ssm项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系结构,…

Spring Test 常见错误

前面我们介绍了许多 Spring 常用知识点上的常见应用错误。当然或许这些所谓的常用,你仍然没有使用,例如对于 Spring Data 的使用,,有的项目确实用不到。那么这一讲,我们聊聊 Spring Test,相信你肯定绕不开对…

网络编程,IO多路复用

1.使用IO多路复用完成TCP并发服务器 #include<myhead.h> #define SER_PORT 8888 //服务器端口号 #define SER_IP "192.168.124.10" //服务器IP地址int main(int argc, const char *argv[]) {//1、创建用于连接的套接字int sfd socket…

Elasticsearch从入门到精通-02环境搭建

Elasticsearch从入门到精通-02环境搭建 &#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是程序员行走的鱼 &#x1f342;博主从本篇正式开始ES学习&#xff0c;希望小伙伴可以一起探讨 &#x1f4d6; 本篇主要介绍和大家一块学习一下ES环境搭建,主要包括Elasticsearch、…

材料物理 (HIT) 笔记-2

原内容请参考哈尔滨工业大学何飞教授&#xff1a;https://www.bilibili.com/video/BV18b4y1Y7wd/?p12&spm_id_frompageDriver&vd_source61654d4a6e8d7941436149dd99026962 或《材料物理性能及其在材料研究中的应用》&#xff08;哈尔滨工业大学出版社&#xff09; 三…

mysql如何开启远程访问?

MySQL是一种常见的关系型数据库管理系统&#xff0c;广泛应用于各行各业。默认情况下&#xff0c;MySQL仅允许本地访问&#xff0c;即只能在本地主机上进行数据库操作。有时候我们需要通过远程连接访问MySQL数据库&#xff0c;以便实现更灵活的管理和操作。本文将介绍如何在MyS…

Docker快速入门和部署项目

1&#xff0c;Docker是一个&#xff0c;快速构建、运行、管理应用的工具 。 2&#xff0c;前面我们了解过在Linux操作系统的常见的命令以及如何在Linux中部署一个人单体的项目。感受如何呢&#xff1f;&#xff1f;&#xff1f; 命令太多了&#xff0c;记不住 软件安装包名字复…

java数据结构YZP专栏-----二叉树 平衡二叉树(AVL)

主文章&#xff08;数据结构的索引目录—进不去就说明我还没写完&#xff09;https://blog.csdn.net/grd_java/article/details/122377505 模拟数据结构的网站&#xff1a;https://www.cs.usfca.edu/~galles/visualization/Algorithms.html 源码(码云)&#xff1a;https://gi…

Python onnxruntime推理yolov5和yolov8(最简易版)

支持yolov5和yolov8双模型 其中ChtDeploy中代码如下&#xff1a; import onnxruntime import numpy as np import cv2class ChtDeploy():def __init__(self, img_path, onnx_path, iou_threshold0.45, conf_threshold0.3, detect_w640, detect_h 640):self.img cv2.imread(im…

【LeetCode每日一题】【BFS模版与例题】【二维数组】1293. 网格中的最短路径

BFS基本模版与案例可以参考&#xff1a; 【LeetCode每日一题】【BFS模版与例题】863.二叉树中所有距离为 K 的结点 【LeetCode每日一题】【BFS模版与例题】【二维数组】130被围绕的区域 && 994 腐烂的橘子 思路&#xff1a; 特殊情况&#xff1a; 最短的路径是向下再向…

多维时序 | Matlab实现GRNN广义回归神经网络多变量时间序列预测

文章目录 效果一览文章概述源码设计参考资料效果一览

SpringBoot(详细介绍)

目录 一、简介 1.1、什么是SpringBoot 1.2.、特性 1.3、四大核心 1.4、特点 二、快速入门 2.1、开发流程 2.1.1、创建项目 maven项目 2.1.2、导入场景 场景启动器 2.1.3、主程序 2.1.4、业务 三、Spring Initializr 创建向导 3.1、依赖管理机制 3.1.1、为什么导…

JRebel and XRebel 插件在IDEA中的安装、激活和使用

1、JRebel安装 1、打开idea->setting->plugins->Marketplace 2、搜索插件JRebel and XRebel&#xff0c;点击安装&#xff0c;然后重启idea 如果左侧出现JRebel & XRebel代表已安装 3.离线安装JRebel 根据自己安装的idea版本进行下载电影的jrebel https://plugi…

H5小游戏,象棋

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的,私信本人,发演示地址,可以后再订阅,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、开心消消乐、扑鱼达人、飞机大战等等 <!DOCTYPE html PUBLIC "-//W3C/…

spring学习简单总结(尚硅谷视频

spring学习简单总结 spring-tx-事务注解添加用注解方式进行aop的配置基于注解配置类进行ioc和di的配置 spring-tx-事务注解添加 选择对应事务管理器实现加入到ioc容器 使用注解指定哪些方法需要添加事务 用注解指定方法 用注解方式进行aop的配置 写自己正常的核心业务代…

Python学习 day07(JSON、format()函数)

JSON 各种编程语言存储数据的容器不尽相同&#xff0c;在Python中有字典dict这样的数据类型&#xff0c;而其他语言可能没有对应的字典&#xff0c;为了让不同的语言都能够相互通用的传递数据&#xff0c;JSON就是一种非常良好的中转数据格式&#xff0c;如下&#xff1a; JSON…