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.rhkb.cn/news/269590.html

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

相关文章

Python:关于数据服务中的Web API的设计

搭建类似joinquant、tushare类似的私有数据服务应用,有以下一些点需要注意: 需要说明的是,这里讨论的是web api前后端,当然还有其它方案,thrift,grpc等。因为要考虑到一鱼两吃,本文只探讨web ap…

FreeRTOS学习笔记-基于stm32f103(1)基础知识

一、裸机与RTOS 我们使用的32板子是裸机,又称前后台系统。裸机有如下缺点: 1、实时性差。只能一步一步执行任务,比如在一个while循环中,要想执行上一个任务,就必须把下面的任务执行完,循环一遍后才能执行…

服务器后端是学习java还是php

没有绝对的"最好"语言,每种后端语言都有其适用的场景和特点。以下是几种常用的后端语言: 1. Java:Java是一种通用且强大的语言,广泛用于企业级应用和大型系统。它有很好的性能和可靠性,并且具有优秀的生态系…

C++面试干货---带你梳理常考的面试题(二)

顾得泉:个人主页 个人专栏:《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂,年薪百万! 1.struct 和 class 区别 1.默认访问权限:struct中的成员默认为public,而class中的成员默认为priv…

Python打发无聊时光:13.用pywin32库制作电脑本地快捷应用

第一步:新建一个simple_app.py 装pywin32库 pip install pywin32 新建一个simple_app.py,复制下面代码,或者可以自己设计内容 import tkinter as tkclass AnimatedGUI:def __init__(self, root):self.root rootself.root.title("玩…

初学arp欺骗

首先准备一台靶机这里用虚拟机的win10 已知网关与ip地址(怕误伤) 现在返回kali从头开始 首先探测自己的网关 然后扫内网存活的ip 发现有3台 用nmap扫一下是哪几台 成功发现我们虚拟机的ip 现在虚拟机可以正常访问网络 接下来直接开梭 ip网关 返回虚拟机…

day03_登录注销(前端接入登录,异常处理, 图片验证码,获取用户信息接口,退出功能)

文章目录 1. 前端接入登录1.1 修改前端代码1.2 跨域请求1.2.1 跨域请求简介1.2.2 COSR概述CORS简介CORS原理 1.2.3 CORS解决跨域 2. 异常处理2.1 提示空消息分析2.2 系统异常分类2.3 异常处理2.2.1 方案一2.2.2 方案二 3. 图片验证码3.1 图片验证码意义3.2 实现思路3.3 后端接口…

【学习心得】响应数据加密的原理与逆向思路

一、什么是响应数据加密? 响应数据加密是常见的反爬手段的一种,它是指服务器返回的不是明文数据,而是加密后的数据。这种密文数据可以被JS解密进而渲染在浏览器中让人们看到。 它的原理和过程图如下: 二、响应数据加密的逆向思路 …

docker中hyperf项目配置虚拟域名

在学习hyperf框架时遇到一些问题,这里是直接用了docker环境 下载镜像运行容器 docker run --name hyperf -v /data/project:/data/project -p 9501:9501 -itd -w /data/project --privileged -u root --entrypoint /bin/sh 镜像ID配置docker-compose.yml version…

[译]BNF 表示法:深入了解 Python 的语法

[译]BNF 表示法:深入了解 Python 的语法 原文:《BNF Notation: Dive Deeper Into Python’s Grammar》 https://realpython.com/python-bnf-notation/ 在阅读Python文档的时候,你可能已经遇到过BNF(Backus–Naur form)表示法: 下…

2024年2月24日~2024年3月1日周报(调整网络结构)

文章目录 一、前言二、实验情况2.1 结果展示2.2 灵感收集 三、小结 一、前言 上周学习了数学表达式、了解了DDNet的网络框架。   在本周,寻找改进网络框架与超参数的灵感,并跑代码查看效果。另外,完成了毕业设计开题报告任务。 二、实验情…

web游戏-飞机大战

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

YOLOv6、YOLOv7、YOLOv8网络结构图(清晰版)

承接上一篇博客&#xff1a;YOLOv3、YOLOv4、YOLOv5、YOLOx的网络结构图(清晰版)_yolox网络结构图-CSDN博客 1. YOLOv6网络结构图 2. YOLOv7网络结构图 3. YOLOv8网络结构图

O2O:Offline–Online Actor–Critic

IEEE TAI 2024 paper 1 Introduction 一篇offline to online 的文章&#xff0c;有效解决迁移过程出现的performance drop。所提出的O2AC算法首先在离线阶段添加一项BC惩罚项&#xff0c;用于限制策略靠近专家策略&#xff1b;而在在线微调阶段&#xff0c;通过动态调整BC的权…

手写分布式配置中心(二)实现分布式配置中心的简单版本

这一篇文章比较简单&#xff0c;就是一个增删改查的服务端和一个获取配置的客户端&#xff0c;旨在搭建一个简单的配置中心架构&#xff0c;代码在 https://gitee.com/summer-cat001/config-center 服务端 服务端选择用springboot 2.7.14搭建&#xff0c;设计了4个接口/confi…

物联网主机:为智能交通赋能

物联网&#xff08;IoT&#xff09;技术的发展为智能交通领域带来了许多创新的解决方案。而在物联网应用中&#xff0c;物联网主机起着关键的作用。本文将为大家介绍一款名为E6000的物联网主机&#xff0c;它是一种多协议、多接口的物联网主机&#xff0c;为智能交通系统的建设…

It is also possible that a host key has just been changed

问题&#xff1a;ssh失败&#xff0c;提示如上图 分析: ssh的key存在上图里的路径里。 解决&#xff1a;win10删这个文件C:\Users\admin\.ssh\known_hosts , linux删这个文件.ssh\known_hosts ,或者删除这个文件里的制定ip的那一行&#xff0c;例如“106.1.1.22 ecdsa-sha2-…

FreeRTOS操作系统学习——FreeRTOS工程创建

FreeROTS工程创建 详细步骤 如无特殊情况&#xff0c;大部人都要配置为外部高速时钟 另外&#xff0c;本实验使用了FreeRTOS&#xff0c;FreeRTOS的时基使用的是Systick&#xff0c;而 STM32CubeMX中默认的HAL库时基也是Systick&#xff0c;为了避免可能的冲突&#xff0c;最…

【学习】torchvision.datasets.ImageFolder()

在分类任务中&#xff0c;数据集文件存储往往是如下形式&#xff1a; - train- class1- image1.jpg- image2.jpg...- class2- image1.jpg- image2.jpg......此时&#xff0c;我们想要获取图片和标签&#xff0c;标签即为文件名&#xff08;class1、class2…&#xff09; 可以使…

数据链路层----滑动窗口协议的相关计算

目录 1.窗口大小的相关计算 •停等协议&#xff1a; •后退N帧协议&#xff1a; •选择重传协议&#xff1a; 2.信道利用率相关计算 •停等协议的信道利用率&#xff1a; •连续ARQ&#xff08;后退N帧协议&#xff0c;选择重传协议&#xff09;的信道利用率&#xff1a;…