前言
作为应届生,处于找工作中。今年2月份刚刚整理出来的Spring面试题,时间比较赶就没有按照Spring的模块比如aop、ioc那些分类排序了。总而言之,顺序比较乱,希望大家耐着性子 看。如果实在介意,评论告知,我会视情况作修改的。另外如果大家觉得我找的答案不够清晰,欢迎私信或者评论只出,我看到都会去修改的!
Spring面试题内容
1. 谈谈你对Spring的理解?
Spring框架是一个轻量级的开源框架,是核心容器、数据访问与集成、AOP、Web、消息、测试六个模块的集成,主要是为了简化企业级应用的后台开发,降低耦合性。平时接触到最多的还是IoC和AOP两个特性。IoC指的是控制反转,把对象的创建和依赖关系的维护交给Spring容器去管理。Spring通过工厂模式、反射机制等技术管理对象的作用域和生命周期。AoP一般称为面向切面编程,是面向对象的一种补充,将程序中独立于其他功能的方法抽取出来,使Java开发模块化,仅需专注于主业务即可
2. Spring的特点是什么?
轻 量、控 制 反 转、面 向 切 面 的 编 程(AOP)、容 器、MVC 框 架、事 务 管 理、异 常 处 理
3. Spring的优缺点是什么?
优点
方便解耦,简化开发
可以将对象的创建和依赖关系的维护交给Spring管理。
提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程。
可以通过注解方便的测试程序。
内部提供了对各种优秀框架的直接支持,方便集成各种优秀框架。
降低 JavaEE API 的使用难度
缺点
Spring明明一个很轻量级的框架,却给人感觉大而全
Spring依赖反射,反射影响性能
使用门槛过高,入门Spring需要较长时间
4. Spring由哪些模块组成?
Spring 总共有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器 、 AOP和设备支持 、数据访问与集成 、 Web、 消息 、 测试 6 个模块中
5. 详细讲解一下核心容器(spring context应用上下文) 模块
这是基本的 Spring 模块,提供 spring 框架的基础功能, BeanFactory 是任何以 spring 为基础的应用的核心.它使 Spring 成为一个容器 。BeanFactory是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从真正的应用代码中分离。最常用的就是org.springframework.beans.factory.xml.XmlBeanFactory ,它根据XML文件中的定义加载beans。该容器从XML 文件读取配置元数据并用它去创建一个完全配置的系统或应用。
6. 解释AOP模块
AOP 模块用于 Spring 应用做面向切面的开发,很多支持由 AOP 联盟提供,这样就确保了 Spring和其他 AOP 框架的共通性。AOP模块将元数据编程引入Spring。
7. 解释JDBC抽象和DAO模块
通过使用JDBC抽象和 DAO 模块,保证数据库代码的简洁,并能避免数据库资源错误关闭导致的问题, 它在各种不同的数据库的错误信息之上,提供了一个统一的异常访问层。它还利用 Spring 的 AOP 模块给 Spring 应用中的对象提供事务管理服务。
8. 解释对象/关系映射集成(ORM)模块
Spring 通过提供ORM模块,支持我们直接在JDBC 之上使用一个对象/关系映射(ORM)工具。
Spring 支持集成主流的 ORM 框架。
Spring 的事务管理同样支持主流的 ORM 框架及 JDBC。
9. 解释WEB模块
Spring 的 WEB 模块是构建在 application context 模块基础之上,提供一个适合 web 应用的上下文。
10. Spring配置文件
Spring 配置文件是个XML文件,这个文件包含了类信息,描述了如何配置它们,以及如何相互调用。
11. 什么是Spring IoC 容器。
控制反转即IoC ,它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现组件对象的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。Spring IOC 负责创建对象,管理对象,装配对象,配置对象,并且管理这些对象的整个生命周期。
12. 控制反转(IoC)有什么作用
管理对象的创建和依赖关系的维护。
方便解耦,由容器去维护具体的对象
托管了类的产生过程。
13.控制反转(IoC)的优点是什么?
把应用的代码量降到最低。
它使应用容易测试,单元测试不再需要单例和JNDI查找机制。
最小的代价和最小的侵入性使松散耦合得以实现。
IoC容器支持加载服务时的饿汉式初始化和懒加载。
14. Spring IoC 的实现机制
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
15. Spring 的 IoC支持哪些功能
依赖注入
依赖检查
自动装配
支持集合
指定初始化方法和销毁方法
支持回调某些方法
16. ApplicationContext 通常的实现是什么?
FileSystemXmlApplicationContext容器 :此容器从一个XML 文件中加载 beans 的定义,XML Bean配置文件的全路径名必须提供给它的构造函数 。
ClassPathXmlApplicationContext容器:此容器从一个 XML 文件中加载 beans 的定义,需要正确设置 classpath 。因为这个容器将在 classpath 里面找 bean 的配置。
WebXmlApplicationContext容器:此容器加载一个 XML 文件,此文件定义了一个 WEB 应用的所有 bean。
17. BeanFactory和 Application contexts 有什么区别?
BeanFactory:是Spring里面最低层的接口,提供了最简单的容器的功能,而且只提供了实例化对象和拿对象的功能;ApplicationContext:是应用上下文,继承BeanFactory接口,它是Spring的一个更高级的容器,提供了更多的有用的功能,这些功能包括;国际化访问资源,如URL和文件载入多个有继承关系的上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层 消息发送和响应机制AOP两者的区别在于装载beans时的区别:BeanFactory在启动的时候不会去实例化Bean,当Spring应用中有从容器拿Bean的时候才会去实例化;ApplicationContext在启动的时候就把所有的Bean全部实例化了,它还可以通过配置来实现Bean的延迟实例化,所以实际开发中推荐使用Application contexts。
18. 一个 Spring 的应用看起来像什么?
一个定义了一些功能的接口 。
Spring AOP
Spring 的 XML 配置文件
使用以上功能的客户端程序
19. 什么是Spring的依赖注入(DI)?
依赖注入是组件之间的依赖关系,由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例化,并注入到应用系统中的各个关联的组件之中。组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
20. 依赖注入的基本原则?
应用组件不应该负责查找资源或者其他依赖的协作对象。
配置对象的工作应该由IoC容器负责,查找资源的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。
IoC容器全权负责组件的装配,它会把符合依赖关系的对象通过属性或者是构造器传递给需要的对象。
21. 依赖注入有什么优势
查找定位操作与应用代码完全无关。
不依赖于容器的API,可以很容易地在任何容器以外使用应用对象。
不需要特殊的接口,绝大多数对象可以做到完全不必依赖容器。
22. 有哪些不同类型的依赖注入方式,推荐使用哪种?
构造器注入、接口注入和 Setter 方法注入。最好的解决方案是用构造器参数实现强制依赖, setter 方法实现可选依赖。
23. 什么是 Spring Beans ?
Spring beans 是那些形成 Spring 应用主干的 java 对象,它们被 Spring IOC 容器初始化,装配,和管理,这些 beans 通过容器中配置的元数据创建。
24. 一个 Spring Bean 定义包含什么?
一个 Spring Bean 的定义包含容器必知的所有配置元数据,包括如何创建一个bean,它的生命周期详情及它的依赖。
25. 如何给 Spring 容器提供配置元数据?
XML 配置文件。
基于注解的配置。
基于 java 的配置。
26. 你怎样定义类的作用域?
当在 Spring 里定义一个
27. 解释 Spring 支持的几种bean的作用域?
singleton作用域 : 是spring默认的作用域,bean 在每个 Spring ioc 容器中只有一个实例。
prototype作用域:一个 bean 的定义可以有多个实例,但该作用域谨慎使用,频繁创建和销毁会严重影响性能。
request作用域:每次 http 请求都会创建一个 bean, 该作用域仅在基于 web 的 Spring Application Context 情况下有效。
session作用域:在一个 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring Application Context 情况下有效 。
global-session作用域:在一个全局的 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring Application Context 情况下有效。
28. Spring 框架中的单例 bean是线程安全的吗?
Spring 框架中的单例 bean 不是线程安全的,spring 中的 bean 默认是单例模式,Spring框架并没有对单例 bean 进行多线程的封装处理。实际上大部分时候 spring bean 是无状态的(比如 dao类),某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了。最简单的就是改变 bean 的作用域,把“singleton”变更为“prototype”,这样请求 bean 相当于 new Bean()了,就可以保证线程安全了。
29. 解释 Spring 框架中 bean 的生命周期
Spring 容器从XML 文件中读取 bean 的定义,并实例化 bean。Spring 根据 bean 的定义填充所有的属性。
如果 bean 实现了 BeanNameAware 接口.Spring 传递 bean 的ID到 setBeanName 方法。
如果 Bean 实现了 BeanFactoryAware 接口, Spring 传递 beanFactory 给 setBeanFactory 方法。
如果有任何与 bean 相关联的 BeanPostProcessors, Spring 会在 postProcesserBeforeInitialization() 方法内调用它们.
如果 bean 实现 IntializingBean 了,调用它的 afterPropertySet 方法,如果 bean 声明了初始化方法,调用此初始化方法。
如果有 BeanPostProcessors 和 bean 关联,这些 bean 的 postProcessAfterInitialization() 方法将被调用。
如果 bean 实现了 DisposableBean 它将调用 destroy() 方法。
30. 哪些是重要的 bean 生命周期方法?你能重载它们吗?
有两个重要的 bean生命周期方法,第一个方法是 setup,它是在容器加载 bean 的时候被调用。第二个方法是teardown ,它是在容器卸载类的时候被调用。Bean 标签有两个重要的属性(init-method和destroy-method),可以通过这两个属性定制初始化和注销方法,它们也有相应的注解(@PostConstruct和@PreDestroy )。
31. 什么是 Spring 的内部 bean(什么是Spring inner beans)?
当一个 bean 仅被用作另一个 bean 的属性时,它能被声明为一个内部 bean。为了定义内部 bean,在 Spring 的基于 XML 的配置元数据中,可以在
32. 在 Spring 中如何注入一个 java 集合?
list类型用于注入一列值,允许有相同的值。
set 类型用于注入一组值,不允许有相同的值。
map类型用于注入一组键值对,键和值都可以为任意类型。
类型用于注入一组键值对,键和值都只能为 String 类型。
33. 什么是bean的装配?
bean的装配是指在Spring 容器中把bean组装到一起,前提是容器需要知道bean的依赖关系,通过依赖注入来把它们装配到一起。
34. 什么是 bean 的自动装载?
Spring 容器能够自动装配相互合作的 bean,这意味着容器不需要
35. 解释不同方式的自动装配
no方式:是默认的方式,不进行自动装配,通过显式设置 ref 属性来进行装配。
byName方式:是通过参数名自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byname之后,容器试图匹配、装配和该 bean 的属性具有相同名字的 bean。
byType方式: 是通过参数类型自动装配,Spring 容器在配置文件中发现 bean 的 autowire 属性被设置成 byType之后,容器试图匹配、装配和该 bean 的属性具有相同类型的 bean。如果有多个 bean 符合条件,则抛出错误。
constructor方式:这个方式类似于 byType方式,但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。
autodetect方式:首先尝试使用 constructor 方式来自动装配,如果无法工作,则使用 byType 方式。
36. 自动装配有哪些局限性?
仍需用
不能自动装配简单的属性,如基本数据类型,String 字符串和类。
模糊特性 :自动装配不如显式装配精确,如果有可能,建议使用显式装配。
37. 你可以在Spring中注入一个null 和一个空字符串吗?
可以。
38. 什么是基于 java 的 Spring 注解配置?给一些注解的例子
基于 Java 的配置,允许在少量的 Java 注解的帮助下,进行大部分 Spring 配置而非通过 XML 文件。以@Configuration注解为例,它用来标记类可以当做一个 bean 的定义,被 Spring IOC 容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个 bean 注册进 Spring 应用上下文。
39. 什么是基于注解的容器配置
相对于 XML 文件,注解型的配置依赖于通过字节码元数据装配组件,而非尖括号的声明。开发者通过在相应的类,方法或属性上使用注解的方式,直接在组件类中进行配置,而不是使用XML文件的方式表述 bean 的装配关系。
40. 怎样开启注解装配?
注解装配在默认情况下是不开启的,为了使用注解装配,必须在 Spring 配置文件中配置context:annotation-config/元素 。
41. @Required 注解
@Required 注解表明 bean 的属性必须在配置的时候设置,通过一个 bean 定义的显式的属性值或通过自动装配。若 @Required 注解的bean 属性未被设置,容器将抛出异常。
- @Autowired 注解
@Autowired 注解提供了更细粒度的控制,包括在何处以及如何完成自动装配。它的用法和 @Required 注解一样,修饰 setter 方法、构造器、属性或者具有任意名称和多个参数的方法。
- @Qualifier 注解
当有多个相同类型的 bean 却只有一个需要自动装配时,将 @Qualifier 注解和 @Autowire 注解结合使用以消除这种混淆,指定需要装配的 bean。
- @RequestMapping 注解
@RequestMapping 注解用于将特定 HTTP 请求方法映射到处理相应请求的控制器中的特定类或者方法。
45. @Autowired和@Resource之间的区别
@Autowired注解:默认是按照类型来装配注入的,它要求依赖对象必须存在。
@Resource注解:默认是按照名称来装配注入的,只有找不到与名称匹配的bean才会按照类型来装配注入。
46. 在 Spring 框架中如何更有效地使用JDBC
使用 Spring JDBC 框架,资源管理和错误处理的代价都会被减轻。开发者只需写 statements 从数据库存取数据,JDBC 也可以在 Spring 框架提供的模板类的帮助下更有效地被使用,这个模板叫JdbcTemplate
47. 什么是JdbcTemplate?
JdbcTemplate 类提供了很多便利的方法来解决问题,如把数据库数据转变成基本数据类型或对象,执行写好的或可调用的数据库操作语句,提供自定义的数据错误处理。
48. Spring 对数据访问对象(DAO)的支持?
Spring 对数据访问对象(DAO)的支持旨在简化数据访问技术,方便切换持久层,编码时也不用担心会捕获每种技术特有的异常。
49. Spring 支持的事务管理类型
编程式事务管理: 通过编程的方式管理事务,带来极大的灵活性,但是难以维护 。
声明式事务管理 :可以将业务代码和事务管理分离,只需用注解和 XML 配置来管理事务。
50. Spring事务的实现方式和实现原理
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务管理功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
51. 事务的ACID是指什么?
原子性(Atomic):事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;
一致性(Consistent):事务结束后系统状态是一致的;
隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态;
持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败,通过日志和同步备份可以在故障发生后重建数据。
52. Spring事务的五种事务隔离级别?
DEFAULT:是Spring事务默认的隔离级别,使用数据库默认的事务隔离机制
未提交读(read uncommited):是最低的事务隔离级别,它允许另外一个事务可以读取当前事务未提交的数据。脏读,不可重复读,幻读都有可能发生
已提交读(read commited): 保证一个事务提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。避免了脏读,但是不可重复读和幻读都有可能发生
可重复读(repeatable read):保证一个事务不会修改已经由另一个事务读取但未提交或者未回滚的数据,避免了脏读和不可重复读,但是幻读有可能发生
可串行化(serializable):最严格的事务隔离级别,支持事务串行执行,资源消耗最大,避免了脏读,不可重复读,幻读
- 什么是脏读、不可重复读、幻读?
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如A事务执行过程中,B事务读取了A事务的修改。但是由于某些原因A事务没有完成提交,发生了回滚操作,则B事务所读取的数据是不正确的,这就是脏读。
不可重复读 :表示一个事务读到另一个事务已经提交的updated数据,导致多次查询结果不一致。比如B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,导致B事务的这两次读取出来的数据不一样,这就是不可重复读
幻读 :表示一个事务读到另一个事务已经提交的insert数据,导致多次查询结果不一致。比如B事务读取了两次数据,在这两次的读取过程中A事务添加了数据,导致B事务的这两次读取出来的数据不一样,这就是幻读
54. Spring事务的7种传播级别?
PROPAGATION_REQUIRED级别: 默认的spring事务传播级别,如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则开启一个新的事务。
PROPAGATION_SUPPORTS级别: 如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则使用非事务的方式执行。
PROPAGATION_MANDATORY级别: 如果已经存在一个事务,则支持当前事务。如果当前不存在事务,则抛出异常。
PROPAGATION_REQUIRES_NEW级别: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。
PROPAGATION_NOT_SUPPORTED级别: 总是使用非事务的方式执行,并挂起任何存在的事务
PROPAGATION_NEVER级别: 使用非事务的方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED级别: 如果当前存在事务,则嵌套在事务内执行。如果当前不存在事务,则按PROPAGATION_REQUIRED属性执行。
55. Spring 框架的事务管理有哪些优点 ?
它为不同的事务 API 提供一个不变的编程模式 。
它为编程式事务管理提供了一套简单的 API 而不是一些复杂的事务 API
它支持声明式事务管理。
它可以很好的集成 Spring 的各种数据访问抽象层。
56. 你更倾向使用哪种事务管理类型?
对于声明式事务管理和编程式事务管理,我更喜欢选择声明式事务管理,因为它对应用代码的影响最小,更符合一个无侵入的轻量级容器的思想。
57. 解释 AOP?
AOP:一般称为面向切面编程作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可复用的模块。这个模块被命名为“切面”,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性,可用于权限认证、日志、事务处理等方面。
58. Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?
AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。AspectJ是静态代理的增强,所谓静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。它会在编译阶段将AspectJ切面织入到Java字节码中,运行的时候就是增强之后的AOP对象。Spring AOP使用的是动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象。这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
59. JDK动态代理和CGLIB动态代理的区别?
区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但AspectJ需要特定的编译器进行处理,而Spring AOP代表的动态代理则无需特定的编译器处理。
60. 什么是代理?
代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。
61. 解释一下Spring AOP里面的几个名词?
切面:是通知和切点的结合,通知和切点共同定义了切面的全部内容。 在Spring AOP中,切面可以使用通用类或者在普通类中以 @AspectJ 注解来实现。
连接点:指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。
通知:在Spring AOP中,切面的工作被称为通知,通知是一个在方法执行前或执行后要做的动作。
切点:切点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定切点。
引入:引入允许向现有类添加新方法或属性。
目标对象: 被一个或者多个切面所通知的对象,它通常是一个代理对象。
织入:织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程,织入可以在编译时,加载时,或运行时完成。
62. Spring通知有哪些类型?
前置通知:在一个方法执行之前调用的通知。
后通知: 在方法执行之后调用的通知,无论方法执行是否成功。
返回后通知: 仅当方法成功完成后调用的通知。
抛出异常后通知: 在方法抛出异常退出时调用的通知。
环绕通知: 在方法执行之前和之后调用的通知 。
63. 在 Spring AOP 中,关注点和横切关注点的区别是什么?
关注点是应用中一个模块的行为,一个关注点可能会被定义成一个想实现的功能。
横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用。比如日志,安全和数据传输,几乎是应用的每个模块都需要的功能。
64. 什么是(Aspect)切面?
AspectJ切面是AOP的核心,它将多个类的通用行为封装成可复用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面,根据需求的不同,一个应用程序可以有若干切面。在SpringAOP中,切面通过带有@Aspect注解的类实现。
65. 有几种不同类型的自动代理?
BeanNameAutoProxyCreator
DefaultAdvisorAutoProxyCreator
Metadata autoproxying
66. 解释基于注解的切面实现?
在这种情况下(基于@AspectJ注解的实现),涉及到的切面声明的风格与带有 java5 标注的普通 java 类一致。
67. Spring 框架中都用到了哪些设计模式?
工厂模式:BeanFactory是工厂模式的一个实现,用来创建对象的实例;
单例模式:Spring配置文件定义的Bean默认为单例模式。
代理模式:Spring的AOP功能的实现用到了代理模式;
模板方法模式:用来解决代码重复的问题。比如. RestTemplate模版类, JmsTemplate模版类,
JpaTemplate模版类。
观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。
68. Spring基于XML注入bean的几种方式?
setter方法注入
构造器注入
静态工厂注入
实例工厂注入
69. @Component, @Controller, @Repository, @Service 有何区别?
@Component:是通用注解,其他三个注解是这个注解的拓展,并且各自具有特定的功能
@Controller:是spring-mvc的注解,具有将请求进行转发,重定向的功能
@Service:是业务逻辑层注解,用于标注该类处于业务逻辑层。
@Repository:在持久层中,具有将数据库操作抛出的原生异常翻译转化为Spring的持久层异常的功能
70. Spring如何处理线程并发问题?
一般情况下,只有无状态的Bean才可以在多线程的环境下共享。在Spring中,绝大部分Bean都可以声明为singleton作用域。因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理,解决线程安全问题。ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题,ThreadLocal采用了“空间换时间”的方式,而线程同步机制采用了“时间换空间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
面试题系列文章传送门
有问题欢迎私信指出,我是码农阿斌,一个互联网行业的菜鸟,立志成为一名架构师。https://lianghongbin.blog.csdn.net/