Java面试题
一个努力中的公众号
长的好看的人都关注了
本文为大家带来java最新的面试题均为目前市面公司所问的真题,包含对应答案。
Spring相关
Spring的理解:
控制反转(Ioc):Spring是一个开源框架,为简化java企业级应用开发而生。Spring可以使得简单的JavaBean实现以前只有EJB才能实现的功能。
Spring是遵从约定大于配置的框架,主要的核心在于依赖注入和控制反转。
此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。大耳根图图
IOC(Inversion Of Controll,控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由给Spring框架来管理。IOC在其他语言中也有应用,并非Spring特有。IOC容器是Spring用来实现IOC的载体,IOC容器实际上就是一个Map(key, value),Map中存放的是各种对象。
大耳根图图
依赖注入(DI):
Spring使用JavaBean对象的set方法或者带参数的构造方法为我们创建所需对象时将其所属性自动设置所需要的值的过程,就是依赖注入的思想。
组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。
通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。大耳根图图
面向切面(AOP):
AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,
大耳根图图
Spring Bean的作用域:
1.singleton:唯一bean实例,Spring中的bean默认都是单例的。
2.prototype:每次请求都会创建一个新的bean实例。
3.request:每一次HTTP请求都会产生一个新的bean,
该bean仅在当前HTTP request内有效。
4.session:每一次HTTP请求都会产生一个新的bean,
该bean仅在当前HTTP session内有效。
5.global-session:全局session作用域,仅仅在基于
Portlet的Web应用中才有意义,Spring5中已经没有了。
Portlet是能够生成语义代码(例如HTML)片段的小型Java Web插件。
它们基于Portlet容器,可以像Servlet一样处理HTTP请求。
但是与Servlet不同,每个Portlet都有不同的会话。
Spring Bean线程安全:
大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。
单例bean存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题。
有两种常见的解决方案:
1.在bean对象中尽量避免定义可变的成员变量(不太现实)。
2.在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在
ThreadLocal中(推荐的一种方式)。大耳根图图
Spring Mvc理解:
Spring MVC是当前最优秀的MVC框架,相比于Struts2,Spring MVC使用更加简单和方便,开发效率更高,并且Spring MVC运行速度更快。
MVC是一种设计模式,Spring MVC是一款很优秀的MVC框架。Spring MVC可以帮助我们进行更简洁的Web层的开发,并且它天生与Spring框架集成。
Spring MVC下我们一般把后端项目分为Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前台页面)。大耳根图图
Spring Mvc原理:
1.客户端(浏览器)发送请求,直接请求到DispatcherServlet。
2.DispatcherServlet根据请求信息调用HandlerMapping,
解析请求对应的Handler。
3.解析到对应的Handler(也就是我们平常说的Controller控制器)。
4.HandlerAdapter会根据Handler来调用真正的处理器来处理请求和
执行相对应的业务逻辑。
5.处理器处理完业务后,会返回一个ModelAndView对象,Model
是返回的数据对象,View是逻辑上的View。
6.ViewResolver会根据逻辑View去查找实际的View。
7.DispatcherServlet把返回的Model传给View(视图渲染)。
8.把View返回给请求者(浏览器)。
简单流程
执行原理
Spring 框架使用哪些设计模式:
1.工厂设计模式:Spring使用工厂模式通过BeanFactory和
ApplicationContext创建bean对象。
2.代理设计模式:Spring AOP功能的实现。
3.单例设计模式:Spring中的bean默认都是单例的。
4.模板方法模式:Spring中的jdbcTemplate、
hibernateTemplate等以Template结尾的对数据库操作的类,
就使用到了模板模式。
5.包装器设计模式:我们的项目需要连接多个数据库,而且不
同的客户在每次访问中根据需要会去访问不同的数据库。这种
模式让我们可以根据客户的需求能够动态切换不同的数据源。
6.观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。
7.适配器模式:Spring AOP的增强或通知(Advice)使用到了
适配器模式、Spring MVC中也是用到了适配器模式适配Controller。
Spring 常用注解:
1.声明bean的注解
组件,没有明确的角色
在业务逻辑层使用(service层)
在数据访问层使用(dao层)
在展现层使用,控制器的声明(C)
2.注入bean的注解
:由Spring提供
-330提供 :由JSR
-250提供 :由JSR
都可以注解在set方法和属性上,推荐注解在属性上(一目了然,少写
代码)。
3.java配置类相关注解
声明当前类为配置类,相当于xml形式的Spring配置
(类上)
注解在方法上,声明当前方法的返回值为一个bean,替代xml中的
方式(方法上)
声明当前类为配置类,其中内部组合了 注
解,表明这个类是一个bean(类上)
用于对Component进行扫描,相当于xml中的(类上)
为 与 的组合
注解,可以替代这两个注解
4.切面(AOP)相关注解
Spring支持AspectJ的注解式切面编程。
声明一个切面(类上)
使用 、 、 定义建言(advice),可直接将拦截
规则(切点)作为参数。
在方法执行之后执行(方法上)
在方法执行之前执行(方法上)
在方法执行之前与之后执行(方法上)
声明切点
在java配置类中使用 注解开启Spring对
AspectJ代理的支持(类上)
5. 的属性支持
设置Spring容器如何新建Bean实例(方法上,得有 )
其设置类型包括:
Singleton (单例,一个Spring容器中只有一个bean实例,默认模式),
Protetype (每次调用新建一个bean),
Request (web项目中,给每个http request新建一个bean),
Session (web项目中,给每个http session新建一个bean),
GlobalSession(给每一个 global http session新建一个Bean实例)
在Spring Batch中还有涉及
-250提供,在构造函数执行完之后执行,等价于 由JSR
xml配置文件中bean的initMethod
-250提供,在Bean销毁之前执行,等价于xml配置 由JSR
文件中bean的destroyMethod
6. 注解
为属性注入值(属性上)
"Michael Jackson") (
String name;
Spring Boot的理解:
SpringBoot 是 Spring 开源组织下的子项目,是 Spring 组件一站式解决方案,主要是简化了使用 Spring 的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
大耳根图图
Spring Boot的优点:
1.独立运行
SpringBoot而且内嵌了各种servlet容器,Tomcat、Jetty等,
现在不再需要打成war包部署到容器中,SpringBoot只要打成一
个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内。
2.简化配置
spring-boot-starter-web启动器自动依赖其他组件,简少了
maven的配置。
3.自动配置
SpringBoot能根据当前类路径下的类、jar包来自动配置bean,
如添加一个spring-boot-starter-web启动器就能拥有web的
功能,无需其他配置。
4.无代码生成和XML配置
SpringBoot配置过程中无代码生成,也无需XML配置文件就能完
成所有配置工作,这一切都是借助于条件注解完成的,这也是
Spring4.x的核心功能之一。
5.应用监控
SpringBoot提供一系列端点可以监控服务及应用,做健康检测。
Spring Boot 常用注解:
:
包含了 、 和
注解。其中
让spring Boot扫描到
Configuration类并把它加入到程序上下文。
:
等同于spring的XML配置文件;使用Java代码
可以检查类型安全。
:
自动配置。
:
组件扫描,可自动发现和装配一些Bean。
:
可配合CommandLineRunner使用,在程序启
动后执行一些基础任务。
:
是 和 的合集,
表示这是个控制器bean,并且是将函数的返回值
直接填入HTTP响应体中,是REST风格的控制器。
:
自动导入。
:
获取参数。
:
解决嵌套外链问题。
:
配合spring-boot-starter-data-rest使用。
Spring 事务:
事务是逻辑处理原子性的保证手段,通过使用事务控制,可以极大的避免出现逻辑处理失败导致的脏数据等问题。
事务最重要的两个特性,是事务的传播级别和数据隔离级别。传播级别定义的是事务的控制范围,事务隔离级别定义的是事务在数据库读写方面的控制范围。大耳根图图
Spring 事务传播级别
1.PROPAGATION_REQUIRED:
默认的spring事务传播级别,使用该级别的特点是,
如果上下文中已经存在事务,那么就加入到事务中执
行,如果当前上下文中不存在事务,则新建事务执行。
所以这个级别通常能满足处理大多数的业务场景。
2.PROPAGATION_SUPPORTS:
从字面意思就知道,supports-支持,该传播级别的
特点是,如果上下文存在事务,则支持事务加入事务,
如果没有事务,则使用非事务的方式执行。所以说,
并非所有的包在transactionTemplate.execute中
的代码都会有事务支持。这个通常是用来处理那些并
非原子性的非核心业务逻辑操作。应用场景较少。
3.PROPAGATION_MANDATORY:
该级别的事务要求上下文中必须要存在事务,否则就
会抛出异常!配置该方式的传播级别是有效的控制上
下文调用代码遗漏添加事务控制的保证手段。比如一
段代码不能单独被调用执行,但是一旦被调用,就必
须有事务包含的情况,就可以使用这个传播级别。
4.PROPAGATION_REQUIRES_NEW:
从字面即可知道,new-每次都要一个新事务,该传播
级别的特点是,每次都会新建一个事务,并且同时将
上下文中的事务挂起,执行当前新建事务完成以后,
上下文事务恢复再执行。
这是一个很有用的传播级别,举一个应用场景:现在
有一个发送100个红包的操作,在发送之前,要做一些
系统的初始化、验证、数据记录操作,然后发送100封
红包,然后再记录发送日志,发送日志要求100%的准
确,如果日志不准确,那么整个父事务逻辑需要回滚。
怎么处理整个业务需求呢?就是通过这个
PROPAGATION_REQUIRES_NEW 级别的事务传播控制就
可以完成。发送红包的子事务不会直接影响到父事务的
提交和回滚。
5.PROPAGATION_NOT_SUPPORTED:
这个也可以从字面得知,nots_upported-不支持,当前
级别的特点就是上下文中存在事务,则挂起事务,执行当
前逻辑,结束后恢复上下文的事务。
这个级别有什么好处?可以帮助你将事务极可能的缩小。
我们知道一个事务越大,它存在的风险也就越多。所以在
处理事务的过程中,要保证尽可能的缩小范围。比如一段
代码,是每次逻辑操作都必须调用的,比如循环1000次的
某个非核心业务逻辑操作。这样的代码如果包在事务中,
势必造成事务太大,导致出现一些难以考虑周全的异常情
况。所以这个事务这个级别的传播级别就派上用场了。用
当前级别的事务模板抱起来就可以了。
6.PROPAGATION_NEVER:
该事务更严格,上面一个事务传播级别只是不支持而已,
有事务就挂起,而PROPAGATION_NEVER传播级别要求上
下文中不能存在事务,一旦有事务,就抛出runtime异
常,强制停止执行!这个级别上辈子跟事务有仇。
7.PROPAGATION_NESTED:
字面也可知道,nested-嵌套级别事务。该传播级别特征
是,如果上下文中存在事务,则嵌套事务执行,如果不存
在事务,则新建事务。
Spring 嵌套事务:
嵌套是子事务套在父事务中执行,子事务是父事务的一部
分,在进入子事务之前,父事务建立一个回滚点,叫
save point,然后执行子事务,这个子事务的执行也算
是父事务的一部分,然后子事务执行结束,父事务继续执
行。重点就在于那个save point。
看几个问题就明了了:
如果子事务回滚,会发生什么?
父事务会回滚到进入子事务前建立的save point,然后
尝试其他的事务或者其他的业务逻辑,父事务之前的操
作不会受到影响,更不会自动回滚。
如果父事务回滚,会发生什么?
父事务回滚,子事务也会跟着回滚!为什么呢,因为父事
务结束之前,子事务是不会提交的,我们说子事务是父事
务的一部分,正是这个道理。
那么:事务的提交,是什么情况?
是父事务先提交,然后子事务提交,还是子事务先提交,
父事务再提交?
答案是第二种情况,还是那句话,子事务是父事务的一
部分,由父事务统一提交。
数据隔离级别分为不同的四种:
1、Serializable:
最严格的级别,事务串行执行,资源消耗最大;
2、REPEATABLE READ:
保证了一个事务不会修改已经由另一个事务读取但未提交
(回滚)的数据。避免了“脏读取”和“不可重复读取”的情
况,但是带来了更多的性能损失。
3、READ COMMITTED:
大多数主流数据库的默认事务等级,保证了一个事务不会
读到另一个并行事务已修改但未提交的数据,避免了
“脏读取”。该级别适用于大多数系统。
4、Read Uncommitted:
保证了读取过程中不会读取到非法数据。
Spring 事务配置方式:
第一种方式:每个Bean都有一个代理
第二种方式:所有Bean共享一个代理基类
第三种方式:使用拦截器
第四种方式:使用tx标签配置的拦截器
第五种方式:全注解
Redis
什么是Redis:
Redis(Remote Dictionary Server) )是一个高性能非关系型(NoSQL)的键值对数据库。
Redis 可以存储键和五种不同类型的值之间的映射。键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
与传统数据库不同的是 Redis 的数据是存在内存中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。
另外,Redis 也经常用来做分布式锁。除此之外,Redis 支持事务 、持久化、LUA脚本、LRU驱动事件、多种集群方案。大耳根图图
Redis的优缺点:
优点
1.读写性能优异, Redis能读的速度是110000次/s,写的速度是81000次/s。
2.支持数据持久化,支持AOF和RDB两种持久化方式。
3.支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
4.数据结构丰富,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
5. 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
缺点
1.数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
2.Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
3. 主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,降低了系统的可用性。
4.Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
Redis有哪些数据类型:
1. string 字符串(可以为整形、浮点型和字符串,统称为元素)
2. list 列表(实现队列,元素不唯一,先入先出原则)
3. set 集合(各不相同的元素)
4. hash hash散列值(hash的key必须是唯一的)
5. sort set 有序集合大耳根图图
Redis过期删除策略:
我们都知道,Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。
过期策略通常有以下三种:
定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)
Redis中同时使用了惰性过期和定期过期两种过期策略。
大耳根图图
多线程
什么是线程:
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,可以使用多线程对进行运算提速。
比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成该任务只需10毫秒大耳根图图
什么是线程安全与线程不安全:
通俗的说:加锁的就是是线程安全的,不加锁的就是是线程不安全的
线程安全
线程安全: 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据
时,进行保护,其他线程不能进行访问,直到该线程读取完,其他线程才可使
用。不会出现数据不一致或者数据污染。
一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下
也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安
全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线
程安全的。
线程不安全
线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成
所得到的数据是脏数据。
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可
能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,
而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对
全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是
线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,
否则的话就可能影响线程安全。
乐观锁和悲观锁:
悲观锁
Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。独占锁其实就是一种悲观锁,所以可以说synchronized是悲观锁。
乐观锁
乐观锁( Optimistic Locking)其实是一种思想。相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。