Python进阶教学——多线程高级应用

目录

一、线程间的通讯机制

二、线程中的消息隔离机制

三、线程同步信号量

四、线程池和进程池


一、线程间的通讯机制

1、Queue消息队列

  • 消息队列是在消息的传输过程中保存消息的容器,主要用于不同线程间任意类型数据的共享。
  • 消息队列最经典的用法就是消费者和生成者之间通过消息管道来传递消息,消费者和生成者是不同的线程。生产者往管道中写消息,消费者从管道中读消息,且一次只允许一个线程访问管道。
1.1、常用接口
  • from queue import Queue
    q =Queue(maxsize=0)  # 初始化,maxsize=0表示队列的消息个数不受限制;maxsize>0表示存放限制
    q.get()  # 提取消息,如果队列为空会阻塞程序,等待队列消息
    q.get(timeout=1)  # 阻塞程序,设置超时时间
    q.put()  # 发送消息,将消息放入队列
1.2、演示
  • 使用生产者和消费者的案例进行演示。 
    • from queue import Queue
      import threading
      import time
      def product(q):  # 生产者kind = ('猪肉','白菜','豆沙')for i in range(3):print(threading.current_thread().name,"生产者开始生产包子")time.sleep(1)q.put(kind[i%3])  # 放入包子print(threading.current_thread().name,"生产者的包子做完了")
      def consumer(q):  # 消费者while True:print(threading.current_thread().name,"消费者准备吃包子")time.sleep(1)t=q.get()  # 拿出包子print("消费者吃了一个{}包子".format(t))
      if __name__=='__main__':q=Queue(maxsize=1)# 启动两个生产者线程threading.Thread(target=product,args=(q, )).start()threading.Thread(target=product,args=(q, )).start()# 启动一个消费者线程threading.Thread(target=consumer,args=(q, )).start()
  • 运行结果:

2、Event事件对象

  • 事件对象主要用于通过事件通知机制实现线程的大规模并发。
  • 事件对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,事件对象中的信号标志被设置为假。如果有线程等待一个事件对象,而这个事件对象的标志为假,那么这个线程将会被一直阻塞直到该标志为真。如果一个事件对象的信号标志被设置为真,它将唤醒所有等待该事件对象的线程。如果一个线程等待一个已经被设置为真的事件对象,那么它将忽略这个事件,继续执行。
  • 应用场景:多个线程逐步开始运行时,由于某个条件未满足,则它们都会被阻塞。直到条件满足后,才全部继续执行。
2.1、常用接口
  • import threading  # 使用多线程必要的模块
    event=threading.Event()  # 创建一个evnet对象
    event.clear()  # 重置代码中的event对象,使得所有该event事件都处于待命状态
    event.wait()  # 阻塞线程,等待event指令
    event.set()  # 发送event指令,使得所有设置该event事件的线程执行
2.2、演示 
  •  创建10个线程对象,使用event事件将其全部关联起来。先把它们全部阻塞,再同时运行。
    • import threading,time
      # 自定义的线程类
      class MyThread(threading.Thread):  # 继承threading.Thread类# 初始化def __init__(self,event):super().__init__()  # 调用父类的初始化方法,super()代表父类对象self.event=event  # 将传入的事件对象event绑定到当前线程实例上# 运行def run(self):print(f"线程{self.name}已经初始化完成,随时准备启动...")self.event.wait()  # 阻塞线程,等待event触发print(f"{self.name}开始执行...")
      if __name__=='__main__':event=threading.Event()threads=[]# 创建10个MyThread线程对象,并传入event。这样每个线程都与这个事件相关联[threads.append(MyThread(event)) for i in range(1,11)]event.clear()  # 使得所有该event事件都处于待命状态[t.start() for t in threads]  # 启动所有线程,由于事件未触发,线程都被锁定time.sleep(5)event.set()  # 使得所有设置该event事件的线程执行[t.join for t in threads]  # 等待所有线程结束后再继续执行主线程
  •  运行结果:
  • 多个线程可以绑定同一个事件,在事件触发时统一并发执行。

3、Condition条件对象

  • 条件对象主要用于多个线程间轮流交替执行任务。
3.1、常用接口
  • import threading
    cond=threading.Condition()  # 新建一个条件对象
    self.cond.acquire()  # 获取锁
    self.cond.wait()  # 线程阻塞,等待通知
    self.cond.notify()  # 唤醒其他wait状态的线程
    self.cond.release()  # 释放锁
3.2、演示
  • 创建两个线程,轮流执行完成对话,如下图。
    • import threading
      # 新建一个条件对象
      cond=threading.Condition()
      class thread1(threading.Thread):def __init__(self,cond,name):threading.Thread.__init__(self,name=name)self.cond=conddef run(self):self.cond.acquire()  # 获取锁print(self.name+":一支穿云箭")  # 线程1说的第1句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.wait()  # 线程阻塞,等待通知print(self.name+":山无楞,天地合,乃敢与君决")  # 线程1说的第2句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.wait()  # 线程阻塞,等待通知print(self.name+":紫薇")  # 线程1说的第3句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.wait()  # 线程阻塞,等待通知print(self.name+":是你")  # 线程1说的第4句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.wait()  # 线程阻塞,等待通知print(self.name+":有钱吗,借点?")  # 线程1说的第5句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.release()  # 释放锁
      class thread2(threading.Thread):def __init__(self,cond,name):threading.Thread.__init__(self,name=name)self.cond=conddef run(self):self.cond.acquire()  # 获取锁self.cond.wait()  # 线程阻塞,等待通知print(self.name+":千军万马来相见")  # 线程2说的第1句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.wait()  # 线程阻塞,等待通知print(self.name+":海可枯,石可烂,激情永不散")  # 线程2说的第3句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.wait()  # 线程阻塞,等待通知print(self.name+":尔康")  # 线程2说的第3句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.wait()  # 线程阻塞,等待通知print(self.name+":是我")  # 线程2说的第4句话self.cond.notify()  # 唤醒其他wait状态的线程(通知线程2说话)self.cond.wait()  # 线程阻塞,等待通知print(self.name+":滚!")  # 线程2说的第5句话self.cond.release()
      if __name__=='__main__':thread1=thread1(cond,'线程1')thread2=thread2(cond,'线程2')# 虽然是线程1先说话,但是不能让它先启动。因为线程1先启动的话,发出notify指令,而线程2可能还未启动,导致notify指令无法接收。thread2.start()  # 线程2先执行thread1.start()
  • 运行结果:

二、线程中的消息隔离机制

1、消息隔离

  • 假设有两个线程,线程A种的变量和线程B中的变量值不能共享,这就是消息隔离
  • 那变量名取不一样不就好啦?的确可以。但如果所有的线程都是由一个class实例化出来的对象呢?这样要给每个线程添加不同的变量就显得很麻烦了。
  • 基于上述场景,python提供了threading.local()这个类,可以很方便的控制变量的隔离,即使是同一个变量,在不同的线程中,其值也是不能共享的。

2、演示

  • 设置一个threading.local共享线程内全局变量,然后新建2个线程,分别设置这个threading.local的值,然后再分别打印这两个threading.local的值,确认是否每个线程打印出来的threading.local的值都是不同的。
    • import threading
      local_data=threading.local()  # 定义线程内全局变量
      local_data.name='local_data'  # 初始名称(主线程)
      class MyThread(threading.Thread):def run(self):print("赋值前-子线程:",threading.current_thread(),local_data.__dict__)  # local_data.__dict__:打印对象所有属性# 在子线程中修改local_data.name的值local_data.name=self.nameprint("赋值后-子线程:",threading.current_thread(),local_data.__dict__)
      if __name__=='__main__':print("开始前-主线程:",local_data.__dict__)# 启动两个线程t1=MyThread()t1.start()t1.join()t2=MyThread()t2.start()t2.join()print("开始后-主线程:",local_data.__dict__)
  • 运行结果:
    • 主线程中的local_data被赋值,而子线程开始前的local_data并未赋值,故为空。

三、线程同步信号量

1、简介

  • semaphore信号量是用于控制线程工作数量的一种锁。
  • 我们知道,在访问文件时,一次只能有一个线程写,而可以有多个线程同时读。如果我们要控制同时读取文件的线程个数,就需要使用到同步信号量。
  • 当信号量不为0时,其他线程可以获取该信号量执行任务。每增加一个线程执行任务,信号量就会减一;每减少一个线程执行任务,信号量加一。当信号量为0时,其他线程全部阻塞,直到有线程完成任务释放信号量。

2、演示

  • 建立10个模拟爬取网站内容的线程,利用同步信号量控制每次只能由3个线程执行任务。
    • import threading,time
      # 模拟爬取网站内容的线程
      class HtmlSpider(threading.Thread):def __init__(self,url,sem):super().__init__()self.url=urlself.sem=semdef run(self):time.sleep(2)  # 模拟网络等待print("获取网页内容成功!")self.sem.release()  # 释放信号量
      # 模拟爬取网站链接的线程
      class UrlProducer(threading.Thread):def __init__(self,sem):super().__init__()self.sem=semdef run(self):for i in range(20):self.sem.acquire()  # 获取信号量html_thread=HtmlSpider(f'https://www.baidu.com/{i}',self.sem)  # 模拟20个网址,并爬取内容html_thread.start()
      if __name__=='__main__':sem=threading.Semaphore(value=3)  # 同步信号量url_producer=UrlProducer(sem)  # 创建获取链接的线程对象url_producer.start()  # 启动线程
  • 运行结果:
    • 每三个一组完成任务。

四、线程池和进程池

1、线程池

  • 线程池是一种多线程处理的形式。线程池维护着多个线程,等待着管理者分配可并发执行的任务,这些任务会被分配给空闲的线程。这避免了在处理短时间任务时创建与销毁线程的代价,让创建好的线程得到重复利用。线程池不仅能够保证内核的充分利用,还能防止过分调度。
  • 不使用线程池:
    • 每个线程在创建并且执行完任务后就会被销毁。
  • 使用线程池:
    • 线程池中的线程会一直保留,直到程序结束。
1.1、线程池模块
  • from concurrent.futures import ThreadPoolExecutor  # 线程池模块
    executor = ThreadPoolExecutor(max_workers=3)  # 创建线程池对象,max_worker为最大线程数
    task1=executor.submit()  # 提交需要线程完成的任务
  • 线程池模块的特性:
    • 主线程可以获取某一个线程或任务的状态,以及返回值。
    • 当一个线程完成的时候,主线程能够立即知道。
    • 让多线程和多进程的编码接口一致。
1.2、演示
  • 创建一个线程池,其中包含3个线程,并为该线程池分配4个任务。 
    • from concurrent.futures import ThreadPoolExecutor
      import time
      # 创建一个新的线程池对象,并且指定线程池中最大的线程数为3
      executor = ThreadPoolExecutor(max_workers=3)
      def get_html(times):time.sleep(times)print(f'获取网页信息{times}完毕')return times
      # 通过submit方法提交执行的函数到线程池中,submit函数会立刻返回,不阻塞主线程
      # 只要线程池中有可用线程,就会自动分配线程去完成对应的任务
      task1=executor.submit(get_html,1)
      task2=executor.submit(get_html,2)
      task3=executor.submit(get_html,3)
      task4=executor.submit(get_html,2)  # 多余的任务需要等待线程池中有空闲的线程
  •  运行结果:
1.3、基本方法
1.3.1、done、cancel、result
  • done():检查任务是否完成,并返回结果。但并不知道线程什么时候完成的。
  • cancel():取消任务的执行,该任务没有放入线程池中才能取消。
  • result():拿到任务的执行结果,该方法是一个阻塞方法。
  • 演示
    • from concurrent.futures import ThreadPoolExecutor
      import time
      # 创建一个新的线程池对象,并且指定线程池中最大的线程数为3
      executor = ThreadPoolExecutor(max_workers=3)
      def get_html(times):time.sleep(times)print(f'获取网页信息{times}完毕')return times
      # 通过submit方法提交执行的函数到线程池中,submit函数会立刻返回,不阻塞主线程
      # 只要线程池中有可用线程,就会自动分配线程去完成对应的任务
      task1=executor.submit(get_html,1)
      task2=executor.submit(get_html,2)
      task3=executor.submit(get_html,3)
      task4=executor.submit(get_html,2)  # 多余的任务需要等待线程池中有空闲的线程
      print(task1.done())  # 检查任务是否完成,并返回结果
      print(task4.cancel())  # 取消任务的执行,该任务没有放入线程池中才能取消
      print(task3.result())  # 拿到任务的执行结果,该方法是一个阻塞方法
    • 运行结果:
1.3.2、as_completed
  • as_completed用来检查任务是否完成,并根据任务完成的时间返回结果。
  • 演示
    • from concurrent.futures import ThreadPoolExecutor,as_completed
      import time
      # 创建一个新的线程池对象,并且指定线程池中最大的线程数为3
      executor = ThreadPoolExecutor(max_workers=3)
      def get_html(times):time.sleep(times)print(f'获取网页信息{times}完毕')return times
      urls=[4,2,3]  # 通过urls列表模拟要抓取的url
      # 通过列表推导式改造多线程任务
      all_task=[executor.submit(get_html,url) for url in urls]
      # 按照任务完成顺序返回结果
      for item in as_completed(all_task):  # as_completed是一个生成器,在任务没有完成之前是阻塞的data=item.result()print(f"主线程中获取任务的返回值是:{data}")
    • 运行结果:
1.3.3、map
  • map和as_completed都可以拿到线程执行的结果,但是map也只是按照输入顺序拿到结果,而as_completed是根据任务结束顺序拿到结果。
  • 演示
    • from concurrent.futures import ThreadPoolExecutor
      import time
      # 创建一个新的线程池对象,并且指定线程池中最大的线程数为3
      executor = ThreadPoolExecutor(max_workers=3)
      def get_html(times):time.sleep(times)print(f'获取网页信息{times}完毕')return times
      urls=[4,2,3]  # 通过urls列表模拟要抓取的url
      # map是一个生成器,不需要submit,直接将任务分配给线程池
      for data in executor.map(get_html,urls):print(f"主线程中获取任务的返回值是:{data}")
    • 运行结果:
1.3.4、wait
  • wait可以阻塞主线程,直到满足指定的条件。 return_when指定了需要满足的条件。
  • 演示
    • from concurrent.futures import ThreadPoolExecutor,wait,ALL_COMPLETED
      import time
      # 创建一个新的线程池对象,并且指定线程池中最大的线程数为3
      executor = ThreadPoolExecutor(max_workers=3)
      def get_html(times):time.sleep(times)print(f'获取网页信息{times}完毕')return times
      urls=[4,2,3]  # 通过urls列表模拟要抓取的url
      all_task=[executor.submit(get_html,url) for url in urls]  # 任务列表
      wait(all_task,return_when=ALL_COMPLETED) # 让主线程阻塞,ALL_COMPLETED直到线程池中的所有线程任务执行完毕
      print('代码执行完毕')
    • 运行结果:        

2、进程池

  • 与线程池类似,进程池是一种多进程处理的形式。进程池维护着多个进程,等待着管理者分配可并发执行的任务,这些任务会被分配给空闲的进程。
2.1、进程池模块
  • 可使用的进程池模块有两种,如下所示。 
  • # 使用concurrent futures模块提供的ProcessPoolExecutor来实现进程池
    from concurrent.futures import ProcessPoolExecutor  # 使用方法与线程池的完全一致
    # 使用Pool类来实现进程池
    from multiprocessing
    multiprocessing.Pool()
  •  下面讲解使用Pool类来实现进程池。
2.2、演示
  • 单个进程执行
    • import multiprocessing
      import time
      def get_html(n):time.sleep(n)print(f"子进程{n}获取内容成功")return n
      if __name__=='__main__':pool=multiprocessing.Pool(multiprocessing.cpu_count())  # 设置进程池,进程数量是电脑cpu的核心数result=pool.apply_async(get_html,args=(3,))  # 异步方式调用pool.close()  # 这个方法必须在join前调用pool.join()  # 子进程未执行完前,会阻塞主进程代码print(result.get())  # 拿到子进程执行的结果print("end")
    • 运行结果:
  • 【注】进程数量最好与电脑CPU的核心数相同。
    • join前必须调用close方法。
  • 多个进程执行
    • import multiprocessing
      import time
      def get_html(n):time.sleep(n)print(f"子进程{n}获取内容成功")return n
      if __name__=='__main__':pool=multiprocessing.Pool(multiprocessing.cpu_count())  # 设置进程池,进程数量是电脑cpu的核心数for result in pool.imap(get_html,[4,2,3]):print(f"{result}休眠执行成功!")
    • 运行结果:
      • 返回的结果是按顺序输出的。

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

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

相关文章

Lua脚本

基本语法 注释 print(“script lua win”) – 单行注释 – [[ 多行注释 ]] – 标识符 类似于:java当中 变量、属性名、方法名。 以字母(a-z,A-Z)、下划线 开头,后面加上0个或多个 字母、下划线、数字。 不要用下划线大写字母…

变压器寿命预测(python代码,Logistic Regression模型预测效果一般,可以做对比实验)

1.数据来源官网:Data for: Root cause analysis improved with machine learning for failure analysis in power transformers - Mendeley Data 点Download All 10kb即可下载数据 2.下载下来后是这样 每一列的介绍: Hydrogen 氢气; Oxyge…

01目标检测-问题引入

目录 一、目标检测问题定义 二、目标检测过程中的常见的问题 三、目标检测VS图像分类区别 目标检测: 图像分类: 总结: 四、目标检测VS目标分割 目标分割: 目标检测是计算机视觉领域的一个重要任务,旨在从图像或…

DBeaver 下载、安装与数据库连接(MySQL)详细教程【超详细,保姆级教程!!!】

本文介绍DBeaver 下载、安装与数据库连接(MySQL)的详细教程 一、DBeaver 下载 官网下载地址:https://dbeaver.io/download/ 二、安装 1、双击下载的安装包,选择中文 2、点击下一步 3、点击我接受 4、如下勾选,…

Linux中使用Docker安装ElasticSearch7.10.x集群

使用Docker安装ElasticSearch7.10.x单节点请访问这里 一、集群环境说明 服务器IP地址192.168.137.1,192.168.137.2,192.168.137.3 二、前期准备 1. 拉取镜像 docker pull elasticsearch:7.10.12. 首先需要创建一个用于生成秘钥的初始容器&#xff0…

面试官:我们深入聊聊Java虚拟机吧

哈喽!大家好,我是奇哥,一位专门给面试官添堵的职业面试员 文章持续更新,可以微信搜索【小奇JAVA面试】第一时间阅读,回复【资料】更有我为大家准备的福利哟! 文章目录 前言面试Java虚拟机内存模型垃圾收集器…

Mac版本破解Typora,解决Mac安装软件的“已损坏,无法打开。 您应该将它移到废纸篓”问题

一、修改配置文件 首先去官网选择mac版本下载安装 typora下载 然后打开typora包内容找到 /Applications/Typora.app/Contents/Resources/TypeMark/ 编辑器打开上面文件夹,这里我拉到vscode 找到page-dist/static/js/Licen..如下图 输入 hasActivated"…

Docker--未完结

一.Docker是干什么的 在没亲自使用过之前,再多的术语也仅仅是抽象,只有写的人或者使用过的人能看懂。 所以,作为新手来说,只要知道Docker是用于部署项目就够了,下面展示如何用Docker部署项目及Docker常用命令。 二、…

ajax day4

1、promise链式调用 /*** 目标:把回调函数嵌套代码,改成Promise链式调用结构* 需求:获取默认第一个省,第一个市,第一个地区并展示在下拉菜单中*/let pname axios({url: http://hmajax.itheima.net/api/province,}).t…

DataGrip 2023 年下载、安装教程、亲测可用

文章目录 前言1. 下载2. 安装3、DataGrip 常用操作4 推荐阅读 前言 DataGrip 是 JetBrains 发布的多引擎数据库环境,支持 MySQL 和 PostgreSQL,Microsoft SQL Server 和 Oracle,Sybase,DB2,SQLite,还有 Hy…

【SpringMVC】自定义注解与AOP结合使用

目录 一、SpringMVC之自定义注解 1.1 Java注解简介 1.2 为什么要用注解 1.3 注解的分类 ⭐ 1.3.1 JDK基本注解 1.3.2 JDK元注解 1.3.3 自定义注解 1.4 自定义注解三种使用案例 1.4.1 案例一(获取类与方法上的注解值) 1.4.2 案例二&#xff0…

JS的执行过程

一:错略可分为三个过程: 分词/词法分析阶段解析/语法分析阶段代码执行阶段 二:详细过程 分词/词法分析阶段(Tokenizing/Lexing):首先,Javascript引擎会将代码按照空格、标点、运算符等分成一个…

Windows关闭zookeeper、rocketmq日志输出以及修改rocketmq的JVM内存占用大小

JDK-1.8zookeeper-3.4.14rocketmq-3.2.6 zookeeper 进入到zookeeper的conf目录 清空配置文件&#xff0c;只保留下面这一行。zookeeper关闭日志输出相对简单。 log4j.rootLoggerOFFrocketmq 进入到rocketmq的conf目录 logback_broker.xml <?xml version"1.0&q…

数据结构与算法:树

目录 树 定义 结构 二叉树 定义 结构 形式 满二叉树 完全二叉树 存储 链式存储结构 数组 孩子节点 父节点 应用 查找 维持相对顺序 遍历 深度优先遍历 前序遍历 中序遍历 后序遍历 广度优先遍历 层序遍历 二叉堆 定义 自我调整 操作 插入加点 删…

深度解析shell脚本的命令的原理之rm

rm 是 Unix/Linux 系统中的一个基本命令&#xff0c;用于删除文件或目录。以下是对这个命令的深度分析&#xff1a; 基本操作&#xff1a;rm 命令删除一个或多个文件或目录。这是通过从文件系统中移除链接来完成的。在 Unix/Linux 中&#xff0c;文件是通过链接&#xff08;可以…

SpringMVC-----JSR303以及拦截器

目录 JSR303 什么是JSR303 JSR303的作用 JSR303常用注解 入门使用 拦截器是什么 拦截器的工作原理 拦截器的作用 拦截器的使用 JSR303 什么是JSR303 JSR303是Java为Bean数据合法性校验提供给的标准框架&#xff0c;已经包含在JavaEE6.0中1。 JSR303通过在Bean属性中标…

PostgreSQL 事务并发锁

文章目录 PostgreSQL 事务大家都知道的 ACID事务的基本使用保存点 PostgreSQL 并发并发问题MVCC PostgreSQL 锁机制表锁行锁 总结 PostgreSQL 事务 大家都知道的 ACID 在日常操作中&#xff0c;对于一组相关操作&#xff0c;通常要求要么都成功&#xff0c;要么都失败。在关系…

html实现邮件模版布局-flex布局table布局-demo

邮件模版布局 flex - 布局简单方便 兼容性差 table - 优点 就是兼容性好&#xff0c;其他没有优点 注&#xff1a;使用图片需要png最好&#xff0c;使用svg图google邮箱会出现不能使用的情况 效果图 flex布局 <!DOCTYPE html> <html lang"en" xmlns:th&qu…

Mojo 语言官网

Mojo面向 AI 开发者的新型编程语言&#xff0c;无缝支持CPU、GPU&#xff0c;兼容Python&#xff0c;跟Python类似的语法&#xff0c;但是比Python快68000倍。目前Mojo仅支持Ubuntu&#xff0c;暂不支持Windows和Mac&#xff0c;可以在Mojo Playground先体验一下。 Mojo 语言…

JVM类加载和双亲委派机制

当我们用java命令运行某个类的main函数启动程序时&#xff0c;首先需要通过类加载器把类加载到JVM&#xff0c;本文主要说明类加载机制和其具体实现双亲委派模式。 一、类加载机制 类加载过程&#xff1a; 类加载的过程是将类的字节码加载到内存中的过程&#xff0c;主要包括…