古巴:为生产做准备

“它可以在我的本地机器上运行!” 如今,这听起来像模因,但仍然存在“开发环境与生产环境”的问题。 作为开发人员,您应始终牢记,您的应用程序有一天将在生产环境中开始运行。 在本文中,我们将讨论一些特定于CUBA的事情,这些事情将帮助您避免在应用程序投入生产时出现问题。

编码准则

优先服务

几乎每个CUBA应用程序都实现一些业务逻辑算法。 此处的最佳实践是在CUBA Services中实现所有业务逻辑。 所有其他类:屏幕控制器,应用程序侦听器等应将业务逻辑执行委托给服务。 此方法具有以下优点:

  1. 一处只有一个业务逻辑实现
  2. 您可以从不同位置调用此业务逻辑,并将其公开为REST服务。

请记住,业务逻辑包括条件,循环等。这意味着理想情况下,服务调用应该是单行的。 例如,假设我们在屏幕控制器中具有以下代码:

Item item = itemService.findItem(itemDate);
if (item.isOld()) {itemService.doPlanA(item);
} else {itemService.doPlanB(item);
}

如果您看到这样的代码,请考虑将其从屏幕控制器移至itemService作为单独的方法processOldItem(Date date)因为它看起来像是应用程序业务逻辑的一部分。

由于屏幕和API可以由不同的团队开发,因此将业务逻辑放在一个地方可以帮助您避免生产中应用程序行为的不一致。

无国籍

开发Web应用程序时,请记住它将被多个用户使用。 在代码中,这意味着某些代码可以由多个线程同时执行。 几乎所有应用程序组件:服务,Bean以及事件侦听器都受多线程执行的影响。 此处的最佳做法是使组件保持无状态。 这意味着您不应引入共享的可变类成员。 使用局部变量并将特定于会话的信息保留在应用程序存储中,用户之间不共享这些信息。 例如,您可以在用户会话中保留少量可序列化的数据。

如果需要共享一些数据,请使用数据库或专用的共享内存存储(例如Redis)。

使用记录

有时生产中会出问题。 而且,当发生这种情况时,很难弄清到底是什么导致了故障,您无法调试部署到生产的应用程序。 为了简化您自己的工作,开发人员和支持团队的同伴并帮助您理解问题并能够重现此问题,请始终将日志记录添加到应用程序中。

此外,日志记录还充当被动监视角色。 应用程序重新启动,更新或重新配置后,管理员通常会查看日志以确保一切都已成功启动。

日志记录可能有助于解决可能不是在您的应用程序中发生的问题,而是在与应用程序集成的服务中发生的问题的解决方法。 例如,要弄清楚为什么付款网关拒绝某些交易,您可能需要记录所有数据,然后在与支持团队进行对话时使用它们。

CUBA使用了经过验证的slf4j库软件包作为外观和注销实现。 您只需要向类代码注入日志记录工具,就可以了。

@Inject
private Logger log;

然后只需在您的代码中调用此服务:

log.info("Transaction for the customer {} has succeeded at {}", customer, transaction.getDate());

请记住,日志消息应该有意义并且包含足够的信息以了解应用程序中发生了什么。 在系列文章“干净的代码,干净的日志”中,您可以找到更多关于Java应用程序的日志记录技巧。 另外,我们建议您参阅“ 9个记录的罪过”一文 。

另外,在CUBA中,我们有性能统计日志,因此您始终可以查看应用程序如何消耗服务器资源。 当客户支持开始收到用户对应用程序运行缓慢的投诉时,这将非常有帮助。 通过此登录,您可以更快地找到瓶颈。

处理异常

异常非常重要,因为异常会在您的应用程序出现问题时提供有价值的信息。 因此,第一条规则-永远不要忽略例外。 使用log.error()方法,创建有意义的消息,添加上下文和堆栈跟踪。 该消息将是您用来标识发生了什么的唯一信息。

如果您有代码约定,请在其中添加错误处理规则部分。

让我们考虑一个示例–将用户的个人资料图片上传到应用程序。 此个人资料图片将保存到CUBA的文件存储和文件上传API服务中。

这是您不得处理异常的方式:

try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (Exception e) {}

如果发生错误,则没人会知道,当用户看不到个人资料照片时,他们会感到惊讶。

这好一些,但远非理想。

try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (FileStorageException e) {log.error (e.getMessage)}

日志中将出现错误消息,我们将仅捕获特定的异常类。 但是将没有有关上下文的信息:文件的名称是谁,谁曾尝试上传它。 而且,将没有堆栈跟踪,因此很难找到异常发生的位置。 还有一件事–用户不会收到有关该问题的通知。

这可能是一个好方法。

try {fileUploadingAPI.putFileIntoStorage(uploadField.getFileId(), fd); } catch (FileStorageException e) {throw new RuntimeException("Error saving file to FileStorage", e);}

我们知道错误,不要丢失原始异常,添加一条有意义的消息。 调用方法将收到有关异常的通知。 我们可以在消息中添加当前用户名以及可能的文件名,以添加更多上下文数据。 这是CUBA Web模块的示例。

在CUBA应用程序中,由于其分布式特性,您可能对核心模块和Web模块具有不同的异常处理规则。 文档中有一个关于异常处理的特殊部分。 实施该政策之前,请先阅读它。

特定于环境的配置

开发应用程序时,请尝试隔离应用程序代码中特定于环境的部分,然后使用功能切换和配置文件根据环境切换这些部分。

使用适当的服务实施

CUBA中的任何服务都由两部分组成:接口(服务API)及其实现。 有时,实现可能取决于部署环境。 例如,我们将使用文件存储服务。

在CUBA中,您可以使用文件存储来保存已发送到应用程序的文件,然后在服务中使用它们。 默认实现使用服务器上的本地文件系统保留文件。

但是,当您将应用程序部署到生产服务器时,此实现可能不适用于云环境或集群部署配置 。

为了启用特定于环境的服务实现,CUBA支持运行时配置文件 ,该配置文件允许您根据启动参数或环境变量来使用特定服务。

对于这种情况,如果我们决定在生产中使用文件存储的Amazon S3实现,则可以通过以下方式指定bean:

<beans profile="prod"><bean name="cuba_FileStorage" class="com.haulmont.addon.cubaaws.s3.AmazonS3FileStorage"/>
</beans>

设置该属性后,将自动启用S3实现:

spring.profiles.active=prod

因此,在开发CUBA应用程序时,请尝试识别特定于环境的服务,并为每种环境启用正确的实现。 尽量不要编写看起来像这样的代码:

If (“prod”.equals(getEnvironment())) {executeMethodA();
} else {executeMethodB();
}

尝试实现一个单独的服务myService ,该服务具有一个方法executeMethod()和两个实现,然后使用配置文件对其进行配置。 之后,您的代码将如下所示:

myService.executeMethod();

更清洁,更简单,更易于维护。

外部化设置

如果可能,将应用程序设置提取到属性文件中。 如果参数将来可以更改(即使概率很小),请始终对其进行外部化。 避免将连接URL,主机名等作为纯字符串存储在应用程序的代码中,切勿将其复制粘贴。 在代码中更改硬编码值的成本要高得多。 邮件服务器地址,用户的照片缩略图大小,没有网络连接时的重试次数–所有这些都是您需要外部化的属性的示例。 使用[配置接口] https://doc.cuba-platform.com/manual-latest/config_interface_usage.html )并将它们注入您的类中以获取配置值。

利用运行时配置文件将特定于环境的属性保存在单独的文件中。

例如,您在应用程序中使用支付网关。 当然,您不应在开发过程中花费大量金钱来测试功能。 因此,您在本地环境中有一个网关存根,在网关端有一个用于生产前测试环境的测试API,一个为产品提供了真实的网关。 显然,这些环境的网关地址不同。

不要这样写代码:

If (“prod”.equals(getEnvironment())) {gatewayHost = “gateway.payments.com”;
} else if (“test”.equals(getEnvironment())) {gatewayHost = “testgw.payments.com”;
} else {gatewayHost = “localhost”;
}
connectToPaymentsGateway(gatewayHost);

而是定义三个属性文件: dev-app.propertiestest-app.propertiesprod-app.properties并在其中定义database.host.name属性的三个不同值。

之后,定义一个配置接口:

@Source(type = SourceType.DATABASE)
public interface PaymentGwConfig extends Config {@Property("payment.gateway.host.name")String getPaymentGwHost();
}

然后注入接口并在您的代码中使用它:

@Inject
PaymentGwConfig gwConfig;//service codeconnectToPaymentsGateway(gwConfig.getPaymentGwHost());

该代码更简单,并且不依赖于环境,所有设置都在属性文件中,如果更改了某些内容,则不应在代码中搜索它们。

添加网络超时处理

始终认为通过网络进行的服务调用不可靠。 当前用于Web服务调用的大多数库都基于同步阻塞通信模型。 这意味着,如果您从主执行线程调用Web服务,则应用程序将暂停直到收到响应。

即使您在单独的线程中执行Web服务调用,该线程也有可能由于网络超时而永远无法恢复执行。

超时有两种类型:

  1. 连接超时
  2. 读取超时

在应用程序中,这些超时类型应分开处理。 让我们使用与上一章相同的示例-付款网关。 对于这种情况,读取超时可能明显长于连接超时。 银行交易可以处理很长的时间,数十秒,最多几分钟。 但是连接应该很快,因此,值得将连接超时设置为例如10秒。

超时值是要移至属性文件的良好候选者。 并始终为通过网络交互的所有服务设置它们。 以下是服务bean定义的示例:

<bean id="paymentGwConfig" class="com.global.api.serviceConfigs.GatewayConfig"><property name="connectionTimeout" value="${xxx.connectionTimeoutMillis}"/><property name="readTimeout" value="${xxx.readTimeoutMillis}"/>
</bean>

在您的代码中,您应该包括一个特殊部分来处理超时。

数据库准则

数据库是几乎所有应用程序的核心。 在生产部署和更新方面,不破坏数据库非常重要。 除此之外,开发人员工作站上的数据库工作负载显然与生产服务器不同。 这就是为什么您可能想要实施以下描述的一些做法。

生成特定于环境的脚本

在CUBA中,我们生成用于创建和更新应用程序数据库的SQL脚本。 在生产服务器上首次创建数据库之后,一旦模型更改,CUBA框架就会生成更新脚本。

关于生产中的数据库更新,有一个特殊的部分 ,请在首次生产之前阅读它。

最终建议:始终在更新之前执行数据库备份。 如果出现任何问题,这将节省大量时间和精力。

考虑多租户

如果您的项目将成为多租户应用程序 ,请在项目开始时将其考虑在内。

CUBA通过该插件支持多租户,它对应用程序的数据模型和数据库的查询逻辑进行了一些更改。 例如,一个单独的列tenantId被添加到所有特定于Tenant的实体。 因此,所有查询都隐式修改为使用此列。 这意味着在编写本机SQL查询时应考虑此列。

请注意,由于上面提到的特定功能,向在生产环境中运行的应用程序添加多租户功能可能很棘手。 为了简化迁移,请将所有自定义查询保留在同一应用程序层中,最好在服务中或在单独的数据访问层中。

安全注意事项

对于可以被多个用户访问的应用程序,安全性起着重要的作用。 为了避免数据泄漏,未经授权的访问等,您需要认真考虑安全性。 您可以在下面找到一些原则,这些原则将帮助您改善安全性。

安全编码

安全性始于防止问题的代码。 您可以在此处找到有关Oracle提供的安全编码的很好的参考。 在下面,您可以从本指南中找到一些(也许很明显)建议。

准则3-2 / INJECT-2:避免使用动态SQL

众所周知,动态创建的SQL语句(包括不受信任的输入)会受到命令注入的影响。 在CUBA中,您可能需要执行JPQL语句,因此,也请避免使用动态JPQL。 如果需要添加参数,请使用适当的类和语句语法:

try (Transaction tx = persistence.createTransaction()) {// get EntityManager for the current transactionEntityManager em = persistence.getEntityManager();// create and execute QueryQuery query = em.createQuery("select sum(o.amount) from sample_Order o where o.customer.id = :customerId");query.setParameter("customerId", customerId);result = (BigDecimal) query.getFirstResult();// commit transactiontx.commit();}

准则5-1 / INPUT-1:验证输入

在使用之前,必须验证来自不受信任来源的输入。 精心设计的输入可能会导致问题,无论是通过方法参数还是外部流。 其中一些示例是整数值溢出和通过在文件名中包含“ ../”序列的目录遍历攻击。 在CUBA中,除了签入代码外,您还可以在GUI中使用验证器 。

以上只是安全编码原理的几个示例。 请仔细阅读该指南,它将以多种方式帮助您改进代码。

保护个人资料的安全

由于法律要求,某些个人信息应受到保护。 在欧洲,我们有GDPR ,在美国的医疗应用中,有HIPAA要求等。因此,在实施您的应用时要考虑到这一点。

CUBA允许您设置各种权限,并使用角色和访问组限制对数据的访问。 在后者中,您可以定义各种约束条件 ,以防止未经授权访问个人数据。

但是提供访问权限只是确保个人数据安全的一部分。 数据保护标准和行业特定要求中有很多要求。 在规划应用程序的体系结构和数据模型之前,请先查看这些文档。

更改或禁用默认用户和角色

使用CUBA框架创建应用程序时,系统中将创建两个用户: adminanonymous 。 始终在生产环境中更改其默认密码,然后用户才能使用该应用程序。 您可以手动执行此操作,也可以将SQL语句添加到30-....sql初始化脚本中。

使用CUBA文档中的建议,这些建议将帮助您正确配置生产中的角色。

如果您具有复杂的组织结构,请考虑为每个分支机构创建本地管理员 ,而不是在组织级别上创建多个“超级管理员”用户。

将角色导出到生产

在第一次部署之前,通常需要将角色和访问组从开发(或登台)服务器复制到生产服务器。 在CUBA中,您可以使用内置的管理UI来执行此操作,而不必手动执行。

要导出角色和特权,您可以使用Administration -> Roles屏幕。 下载文件后,您可以将其上传到应用程序的生产版本。

对于访问组,有一个类似的过程,但是您需要使用Administration -> Access Groups屏幕。

配置应用

生产环境通常与开发环境以及应用程序配置不同。 这意味着您需要执行一些其他检查,以确保您的应用程序在生产时能够平稳运行。

配置日志

确保已针对生产环境正确配置了日志记录子系统:日志级别已设置为所需级别(通常为INFO),并且在应用程序重新启动时不会删除日志。 您可以参考文档以获取正确的日志设置和有用的记录器参考。

如果使用Docker,请使用Docker卷将日志文件存储在容器外部。

为了进行正确的日志记录分析,您可以部署特殊的工具来收集,存储和分析日志。 例如ELK stack和Graylog 。 建议将日志记录软件安装到单独的服务器上,以避免对应用程序造成性能影响。

在群集配置中运行
可以将CUBA应用程序配置为在群集配置中运行。 如果决定使用此功能,则需要注意您的应用程序体系结构,否则,您可能会从应用程序中得到意外的行为。 我们希望引起您对专门针对集群环境进行调整的最常用功能的注意:

任务调度
如果要在应用程序中执行预定任务(例如每日报告生成或每周电子邮件发送),则可以使用相应的框架内置功能ъ( https://doc.cuba-platform.com/manual-latest /scheduled_tasks.html )。 但是,请想象自己是一位获得了三封相同营销电子邮件的客户。 你快乐吗? 如果您的任务在三个群集节点上执行,则可能会发生这种情况。 为避免这种情况,最好使用CUBA任务计划程序 ,该程序使您可以创建单例任务。

分布式缓存
缓存是可以提高应用程序性能的东西。 有时开发人员尝试缓存几乎所有内容,因为现在内存非常便宜。 但是,当您的应用程序部署在多台服务器上时,缓存将在服务器之间分配,并且应该同步。 同步过程发生在相对较慢的网络连接上,这可能会增加响应时间。 这里的建议–在决定添加更多缓存之前(尤其是在集群环境中),执行负载测试并衡量性能。

结论

CUBA平台简化了开发,您可能会完成开发并开始考虑比预期更早的投入生产。 但是,无论是否使用CUBA,部署都不是一件容易的事。 而且,如果您开始考虑在开发的早期阶段就进行部署并遵循本文所述的简单规则,那么您的生产方式很可能会很顺利,所需的工作量很小,并且不会遇到严重的问题。

翻译自: https://www.javacodegeeks.com/2020/03/cuba-getting-ready-for-production.html

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

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

相关文章

hibernate脏数据_Hibernate脏检查的剖析

hibernate脏数据介绍 持久性上下文使实体状态转换入队 &#xff0c;该实体状态转换在刷新后转换为数据库语句。 对于托管实体&#xff0c;Hibernate可以代表我们自动检测传入的更改并安排SQL UPDATE。 这种机制称为自动脏检查 。 默认的脏检查策略 默认情况下&#xff0c;Hibe…

php组成,php接口有几部分组成?

程序接口&#xff0c;由一套陈述、功能、选项、其它表达程序结构的形式、以及程序师使用的程序或者程序语言提供的数据组成PHP接口(interface)的特点1、接口的方法必须是公开的。2、接口的方法默认是抽象的&#xff0c;所以不在方法名前面加abstract。3、接口可以定义常量&…

java 解析日期格式_日期/时间格式/解析,Java 8样式

java 解析日期格式自Java 几乎 开始以来&#xff0c;Java开发人员就通过java.util.Date类&#xff08;自JDK 1.0起&#xff09;和java.util.Calendar类&#xff08;自JDK 1.1起 &#xff09;来处理日期和时间。 在这段时间内&#xff0c;成千上万&#xff08;甚至数百万&#x…

php第三方登录代码,thinkPHP5项目中实现QQ第三方登录功能

本文实例讲述了thinkPHP5项目中实现QQ第三方登录功能。分享给大家供大家参考&#xff0c;具体如下&#xff1a;最近用thinkPHP 5框架做了一个婚纱店的项目&#xff0c;在开发过程中需要用到第三方登录&#xff0c;腾讯官方给的案例是几个文件相互包含实现的&#xff0c;放到tp5…

mac 显示隐藏文件_如何在Mac上显示隐藏文件?苹果mac显示隐藏文件夹方法

与任何操作系统一样&#xff0c;macOS会将重要文件隐藏起来&#xff0c;以防止意外删除它们并因此而损坏系统。但是&#xff0c;在某些情况下&#xff0c;您可能需要在Mac上显示隐藏文件&#xff0c;例如&#xff0c;浏览“ 库”文件夹并清除旧日志&#xff0c;缓存或其他垃圾文…

分布式虚拟跟踪

跟踪提供了对系统的可见性&#xff0c;使开发人员和操作人员可以在运行时观察应用程序。 当系统不断增长并与更多微服务进行交互时&#xff0c;跟踪变得非常有价值。 在这样的环境中&#xff0c;这些痕迹非常棒&#xff0c;可以定位导致性能下降的故障和瓶颈。 在这篇文章中&a…

php 删除数组的空元素,php删除数组空元素的方法_后端开发

php如何实现自动跳转_后端开发php实现自动跳转的方法&#xff1a;1、通过php内置函数“header”&#xff0c;将http响应头中的“Location”设置为要跳转的URL即可&#xff1b;2、可以在javascript代码中将“window.location.href”指向要跳转的URL即可。php删除数组空元素的方法…

map for循环_JavaScript 用 for 循环太 low?你是不是有什么误解

天要吐槽下&#xff0c;我时不时地看到有些文章说“循环语句不好&#xff0c;你应该用 filter&#xff0c;map 和 reduce ”——每次看到有文章鼓吹&#xff0c;所有需要循环的场景一律用这几个函数式方法&#xff0c;我都恨得牙痒痒。没错&#xff0c;这些函数式方法确实有它们…

简单工程验收单表格_中铁超大型工程项目-123个精细化管理手册配套表格附件,超全...

中铁超大型工程项目-123个精细化管理手册配套表格附件&#xff0c;超全&#xff01;什么是项目精细化&#xff1f;答&#xff1a;工程项目精细化管理是一个系统的管理体系&#xff0c;包含一系列管理制度和办法&#xff0c;除了《工程项目精细化管理办法》这个纲领性文件外&…

判断unsigned long long乘法溢出_信息安全课程17:缓冲区溢出2

在之前所讲述的内容中&#xff0c;都是我们在自己的程序中自行修改的&#xff1b;正常情况下&#xff0c;没有程序员会在自己的代码中这样写——那有没有办法攻击别人正常的程序呢&#xff1f;攻击者怎么样能够影响到不是自己的程序的返回地址呢&#xff1f;以及怎么样通过攻击…

java 保垒机telnet,开源堡垒机系统Teleport

一. teleport简介Teleport是一款简单易用的堡垒机系统&#xff0c;具有小巧、易用的特点&#xff0c;支持 RDP/SSH/SFTP/Telnet 协议的远程连接和审计管理。Teleport由两大部分构成&#xff1a;跳板核心服务WEB操作界面官网地址: https://tp4a.com/ 官网文档: https://docs.…

php背景图片随页面大小改变,css背景图根据屏幕大小自动缩放

css背景图根据屏幕大小自动缩放代码&#xff1a;html,body{margin:0px;padding:0px;}#background { position: fixed;top: 0;left: 0;width: 100%;height: 100%;overflow: hidden;background-color: #211f1f; display:none\8;}#background .bg-photo {position: absolute;top: …

forever不重启 node_运维监控Prometheus,部署安全的node_exporter监控主机

简介prometheus监控系统的时候&#xff0c;是使用pull的方式来获取监控数据&#xff0c;需要被监控端监听对应的端口&#xff0c;prometheus从这些端口服务中拉取对应的数据。node_exporter安全性讨论node_exporter是收集操作系统的指标的一个程序。例如CPU&#xff0c;内存&am…

netbeans ide_IDE:5个最喜欢的NetBeans功能

netbeans ide愉快的发展……。 NetBeans具有许多有趣的功能 &#xff0c;这些功能使开发非常容易&#xff0c;只需很少的步骤&#xff0c;并且可以在非常快速地将产品推向市场的过程中创造出非常高效的环境 。 将我的谈话仅限于五个功能非常困难&#xff0c;而此IDE具有大量有…

那是两个小时我不会回来

正如我之前关于linting主题所说的 &#xff0c;花时间修改代码的好处很有限&#xff0c;因为自动工具告诉您这样做。 更糟糕的是&#xff0c;这些工具并非万无一失。 例如&#xff0c;我们一直在针对完美无害的try-with-resources构造周围的SpotBugs警告中添加排除项&#xff…

oracle 查询不同编号的时间最小记录_投稿 | ORACLE amp; PostgreSql 利用伪列删除完全重复的两行...

作者&#xff1a;缪晓丽DBA、数据库爱好者、从业10年。对 DB2、PostgreSQL、Oracle 均有较长的运维经验。本次演示的 test 表如下&#xff1a;01Oracle 的 rowidSQL 语句&#xff1a;DELETE FROM ( SELECT ROW_NUMBER() OVER (PARTITION BY id ORDER BY id) AS rn, id, name…

addcslashes php,php addcslashes函数怎么用

php addcslashes函数返回在指定字符前添加反斜杠的字符串。其语法是addcslashes(string,characters)&#xff0c;参数string是必须的&#xff0c;规定要转义的字符串&#xff0c;characters是必须&#xff0c;规定要转义的字符或字符范围。addcslashes函数怎么用&#xff1f;作…

struct类型重定义 不同的基类型_C++构造数据类型

结构体(struct)定义&#xff1a;结构体是一个数据类型&#xff0c;是由多个不同类型的数据组成的数据集合。关键字为&#xff1a;struct 。优点&#xff1a;大大减少程序代码的离散性&#xff0c;使程序代码阅读更加符合逻辑。语法&#xff1a;struct 结构体类型名{ 成员类型 成…

php zip怎么安装,php如何安装zip模块?(方法介绍)

php 安装zip模块为php安装zip扩展wget http://pecl.php.net/get/zip-1.13.5.tgz tar -zvxf zip-1.13.5.tgz cd zip-1.13.5 /home/xxx/php/bin/phpize出现错误&#xff1a;Cannot find autoconf. Please check your autoconf installation and the $PHP_AUTOCONF environment va…

vs如何写多线程_java中的多线程的示例

在讨论多线程之前&#xff0c;让我们先讨论线程。线程是进程中轻量级的最小部分&#xff0c;可以与同一进程的其他部分(其他线程)并发运行。线程是独立的&#xff0c;因为它们都有独立的执行路径&#xff0c;这就是为什么如果一个线程中发生异常&#xff0c;它不会影响其他线程…