Java面经汇总【背诵版】囊括了基本所有考点【自己总结的】
1、牛客刷题
●RabbitMQ有以下几种工作模式 :
简单模式:一个生产者,一个消费者
work模式:一个生产者,多个消费者,每个消费者获取到的消息唯一。
订阅模式:一个生产者发送的消息会被多个消费者获取。
路由模式:发送消息到交换机并且要指定路由key ,消费者指定路由key将队列绑定到交换机
topic模式:将路由键和某模式进行匹配,此时队列需要绑定在一个模式上
RPC
String,Stringbuffer,StringBuilder的区别
String:
- String类是一个不可变的类,一旦创建就不可以修改。
- String是final类,不能被继承
- String实现了equals()方法和hashCode()方法
StringBuffer:
- 继承自AbstractStringBuilder,是可变类。
- StringBuffer是线程安全的
- 可以通过append方法动态构造数据。
StringBuilder:
继承自AbstractStringBuilder,是可变类。
StringBuilder是非线性安全的。
执行效率比StringBuffer高。
String s 与new String的区别
String str ="whx"; String newStr =new String ("whx");
String str ="whx"
先在常量池中查找有没有"whx" 这个对象,如果有,就让str指向那个"whx".如果没有,在常量池中新建一个“whx”对象,并让str指向在常量池中新建的对象"whx"。
String newStr =new String ("whx");
是在堆中建立的对象"whx" ,在栈中创建堆中"whx" 对象的内存地址。
反射的原理,反射创建类实例的三种方式是什么
Java反射机制:
Java 的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法; 并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制
获取 Class 类对象三种方式:
- 使用 Class.forName 静态方法
- 使用类的.class 方法
- 使用实例对象的 getClass() 方法
JDK动态代理与cglib实现的区别
- java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler处理。
- cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类
- cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final
谈谈序列化与反序列化
- 序列化是指将对象转换为字节序列的过程,而反序列化则是将字节序列转换为对象的过程。
- Java对象序列化是将实现了Serializable接口的对象转换成一个字节序列,能够通过网络传输、文件存储等方式传输 ,传输过程中却不必担心数据在不同机器、不同环境下发生改变,也不必关心字节的顺序或其他任何细节,并能够在以后将这个字节序列完全恢复为原来的对象。
分布式结构,怎么保证数据一致性
规避分布式事务——业务整合
经典方案 - eBay 模式
2、数据库相关
MySQL
●SQL注入
SQL注入就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
1)SQL注入攻击的总体思路
●寻找到SQL注入的位置
●判断服务器类型和后台数据库类型
●针对不同的服务器和数据库特点进行SQL注入攻击
2)应对方法
●使用正则表达式过滤传入的参数
●参数绑定
●使用预编译手段,绑定参数是最好的防SQL注入的方法。
● 请你说明一下 left join 和 right join 的区别?
left join(左联接) :返回包括左表中的所有记录和右表中联结字段相等的记录
right join(右联接) :返回包括右表中的所有记录和左表中联结字段相等的记录
●事务是什么
事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。
● 数据库ACID的特性。
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性指事务前后数据的完整性必须保持一致。
隔离性指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。
持久性是指一个事务一旦提交,它对数据库中数据的改变就是永久性的,即便数据库发生故障也不应该对其有任何影响。
● 请你介绍一下,数据库的三个范式?
第一范式(1NF):强调的是列的原子性,即列不能够再分成其他几列。
第二范式(2NF):首先满足 1NF,另外包含两部分内容,一是表必须有一个主键;二是没有包含在主键中的列必须完全依赖于主键,而不能只依赖于主键的一部分。
第三范式(3NF):首先是满足2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况
● 请你介绍一下数据库的隔离级别
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读 | 可能 | 可能 | 可能 |
已提交读 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 可能 |
可串行化 | 不可能 | 不可能 | 不可能 |
未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据。
已提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)。
可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读。
串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。
数据库的脏读、幻读、不可重复读
1.脏读:
指一个事务A正在访问数据,并且对该数据进行了修改,但是这种修改还没有提交到数据库中(也可能因为某些原因Rollback了)。这时候另外一个事务B也访问这个数据,然后使用了这个被A修改的数据,那么这个数据就是脏的,并不是数据库中真实的数据。这就被称作脏读。(事务A读到了事务B未提交的数据)
解决办法:把数据库事务隔离级别调整到READ_COMMITTED
即让用户在更新时锁定数据库,阻止其他用户读取,直到更新全部完成才让你读取。
2.幻读:
指一个事务A对一个表中的数据进行了修改,而且该修改涉及到表中所有的数据行;同时另一个事务B也在修改表中的数据,该修改是向表中插入一行新数据。那么经过这一番操作之后,操作事务A的用户就会发现表中还有没修改的数据行,就像发生了幻觉一样。这就被称作幻读。(事务A修改数据,事务B插入数据,A发现表中还没有修改的数据行)
解决办法:把数据库事务隔离级别调整到SERIALIZABLE_READ
3.不可重复读:
指在一个事务A内,多次读同一个数据,但是事务A没有结束时,另外一个事务B也访问该同一数据。那么在事务A的两次读数据之间,由于事务B的修改导致事务A两次读到的数据可能是不一样的。这就发生了在一个事务内两次读到的数据不一样,这就被称作不可重复读。(事务A多次读数据,事务B访问数据,A读到了B修改的数据,导致两次读到的数据不一样)
解决办法:把数据库事务隔离级别调整到REPEATABLE_READ
级别高低:脏读 < 不可重复读 < 幻读
所以设置了最高级别的SERIALIZABLE_READ就不需要设置其他的了,即解决了幻读问题那么脏度和不可重复读自然就都解决了。
● 请你简单介绍一下,数据库水平切分与垂直切分
垂直拆分就是要把表按模块划分到不同数据库表中,单表大数据量依然存在性能瓶颈
水平切分就是要把一个表按照某种规则把数据划分到不同表或数据库里。
通俗理解:水平拆分行,行数据拆分到不同表中, 垂直拆分列,表数据拆分到不同表中。
● 请你讲讲 Statement 和 Prepared Statement 的区别?哪个性能更好?
与Statement相比,①PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性(减少SQL注射攻击的可能性);②PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全;③当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快
mysql数据库的索引类型
索引数据结构:二叉树、红黑树、hash表、B-tree
1、普通索引当一张表,把某个列设为主键的时候,则该列就是主键索引
2、唯一索引索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
3、主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。
4、组合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。
5、全文索引主要用来查找文本中的关键字,而不是直接与索引中的值相比较。
InnoDB索引实现(聚集)
- 数据文件本身就是索引文件
- 表数据文件本身就是按B+树组织的一个索引结构文件
- 聚集索引的叶子节点包含了完整的数据记录
- 表必须有主键,且推荐使用整型的自增主键
- 普通索引结构叶子节点存储的是主键值
InnoDB主键索引查找流程:通过.ibd文件找到对应的索引,索引的value即为那行对应的完整数据
聚集索引和非聚集索引的区别?
聚集索引:表中那行数据的索引和数据都合并在一起了。
非聚集索引:表中那行数据的索引和数据是分开存储的。
一个 SQL 执行的很慢,我们要分两种情况讨论:
1、偶尔很慢,则有如下原因
(1)、数据库在刷新脏页,例如 redo log 写满了需要同步到磁盘。
(2)、执行的时候,遇到锁,如表锁、行锁。
2、这条 SQL 语句一直执行的很慢,则有如下原因。
(1)、没有用上索引:例如该字段没有索引;由于对字段进行运算、函数操作导致无法用索引。
(2)、数据库选错了索引。
Redis
redis常见的数据结构以及应用场景:
String:key -value缓存应用,最常规的set/get操作,value可以是String也可以是数字。一般做一些复杂的计数功能的缓存。
Hash:field-value映射表,存储用户信息和商品信息
List:list分页查询
Set:实现差,并,交集操作,比如共同喜好等
Sorted set:用户列表,礼物排行榜,弹幕消息
缓存雪崩:
缓存同一时间大面积失效,所有请求都落到数据库造成短时间内承受大量请求而崩掉
如何解决缓存雪崩?在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
对于“Redis挂掉了,请求全部走数据库”这种情况,我们可以有以下的思路:
事发前:实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生。
事发中:设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库***掉(起码能保证我们的服务还是能正常工作的)
事发后:redis持久化,重启后自动从磁盘上加载数据,快速恢复缓存数据。
缓存穿透:
恶意请求缓存中不存在的数据,所有求情都落到数据库造成短时间内承受大量请求而崩掉
如何解决缓存穿透?
(一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试
(二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。
(三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。
Redis和数据库双写一致性?
首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。
采用延时双删策略
(1)先淘汰缓存
(2)再写数据库(这两步和原来一样)
(3)休眠1秒,再次淘汰缓存
Redis的内存淘汰机制:Redis提供了8种内存淘汰策略
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
- volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
- allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
如何解决 Redis 的并发竞争 Key 问题
所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,最后执行的顺序和我们期望的顺序不同,导致了结果的不同!推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。
redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进行恢复)
1、快照(snapshotting)持久化(RDB)
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis主从结构,主要用来提高Redis性能),还可以将快照留在原地以便重启服务器的时候使用。快照持久化是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置
2、AOF(append-only file)持久化
与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF方式的持久化,可以通过appendonly参数开启.Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 aof-use-rdb-preamble
开启)。
● 请问,为什么 redis 读写速率快、性能好?
Redis是纯内存数据库,相对于读写磁盘,读写内存的速度就不是几倍几十倍了,一般hash查找可以达到每秒百万次的数量级。
多路复用IO,“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)。可以直接理解为:单线程的原子操作,避免上下文切换的时间和性能消耗;加上对内存中数据的处理速度,很自然的提高redis的吞吐量。
3、SSM
1、spring
● 请问什么是IoC和DI?并且简要说明一下DI是如何实现的?
IoC叫控制反转,DI叫依赖注入。控制反转是把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。"控制反转"就是对组件对象控制权的转移,从程序代码本身转移到了外部容器,由容器来创建对象并管理对象之间的依赖关系。依赖注入的基本原则是应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由容器负责,查找资源的逻辑应该从应用组件的代码中抽取出来,交给容器来完成。DI是对IoC更准确的描述,即组件之间的依赖关系由容器在运行期决定,即由容器动态的将某种依赖关系注入到组件之中。
依赖注入可以通过setter方法注入(设值注入)、构造器注入和接口注入三种方式来实现,Spring支持setter注入和构造器注入,通常使用构造器注入来注入必须的依赖关系,对于可选的依赖关系,则setter注入是更好的选择,setter注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。
依赖注入是从应用程序的角度在描述:应用程序依赖容器创建并注入它所需要的外部资源;
控制反转是从容器的角度在描述:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。
● 请说明一下springIOC原理是什么?如果你要实现IOC需要怎么做?请简单描述一下实现步骤?
①IoC这是spring的核心,由spring来负责控制对象的生命周期和对象间的关系。
IoC的一个在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI来实现的。比如对象A需要操作数据库,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像***一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
②实现IOC的步骤
定义用来描述bean的配置的Java类、解析bean的配置、遍历存放HashMap对象
● 请谈一谈Spring中自动装配的方式有哪些?
- no:不进行自动装配,手动设置Bean的依赖关系。
- byName:根据Bean的名字进行自动装配。
- byType:根据Bean的类型进行自动装配。
- constructor:类似于byType,不过是应用于构造器的参数,如果正好有一个Bean与构造器的参数类型相同则可以自动装配,否则会导致错误。
- autodetect:如果有默认的构造器,则通过constructor的方式进行自动装配,否则使用byType的方式进行自动装配。
bean的生命周期
● 请简要说明一下IOC和AOP是什么?
控制反转(IoC)与依赖注入(DI)是同一个概念,引入IOC的目的:(1)脱开、降低类之间的耦合;(2)倡导面向接口编程、实施依赖倒换原则; (3)提高系统可插入、可测试、可修改等特性。
具体做法:(1)将bean之间的依赖关系尽可能地抓换为关联关系;
(2)将对具体类的关联尽可能地转换为对Java interface的关联,而不是与具体的服务对象相关联;
(3)Bean实例具体关联相关Java interface的哪个实现类的实例,在配置信息的元数据中描述;
(4)由IoC组件(或称容器)根据配置信息,实例化具体bean类、将bean之间的依赖关系注入进来。
AOP,即面向切面编程,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",所谓"切面"是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开。
● 请问Spring支持的事务管理类型有哪些?以及你在项目中会使用哪种方式?
Spring支持编程式事务管理和声明式事务管理。许多Spring框架的用户选择声明式事务管理,因为这种方式和应用程序的关联较少,因此更加符合轻量级容器的概念。声明式事务管理要优于编程式事务管理,尽管在灵活性方面它弱于编程式事务管理,因为编程式事务允许你通过代码控制业务。
● 你如何理解AOP中的连接点(Joinpoint)、切点(Pointcut)、增强(Advice)、引介(Introduction)、织入(Weaving)、切面(Aspect)这些概念?
a. 连接点(Joinpoint):程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。
b. 切点:如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。
c. 增强(Advice):增强是织入到目标类连接点上的一段程序代码。
d. 引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。
e. 织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,AOP有三种织入方式
f. 切面:切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。
● 请问AOP的原理是什么?
AOP指面向切面编程,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。通常使用AspectJ的编译时增强实现AOP,AspectJ是静态代理的增强,所谓的静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类
● 请问aop的应用场景有哪些?
Authentication 权限 ,Caching 缓存 ,Context passing 内容传递 ,Error handling 错误处理 ,Lazy loading 懒加载 ,Debugging 调试 ,logging, tracing, profiling and monitoring 记录跟踪 优化 校准,Performance optimization 性能优化 ,Persistence 持久化 ,Resource pooling 资源池 ,Synchronization 同步,Transactions 事务。
● Spring框架为企业级开发带来的好处有哪些?
- 非侵入式:支持基于POJO的编程模式,不强制性的要求实现Spring框架中的接口或继承Spring框架中的类。
- IoC容器:IoC容器帮助应用程序管理对象以及对象之间的依赖关系,对象之间的依赖关系如果发生了改变只需要修改配置文件而不是修改代码,因为代码的修改可能意味着项目的重新构建和完整的回归测试。
- AOP:将所有的横切关注功能封装到切面中,通过配置的方式将横切关注功能动态添加到目标代码上,进一步实现了业务逻辑和系统服务之间的分离。另一方面,有了AOP程序员可以省去很多自己写代理类的工作。
- MVC:Spring的MVC框架为Web表示层提供了更好的解决方案。
- 事务管理:Spring以宽广的胸怀接纳多种持久层技术,并且为其提供了声明式的事务管理,在不需要任何一行代码的情况下就能够完成事务管理。
- 其他:Spring为Java企业级开发提供了一站式选择,你可以在需要的时候使用它的部分和全部,更重要的是,甚至可以在感觉不到Spring存在的情况下,在你的项目中使用Spring提供的各种优秀的功能。
Aop实现的几种方式:
第一种:静态织入,即在编译时,就将各种涉及AOP拦截的代码注入到符合一定规则的类中,编译后的代码与我们直接在RealA调用属性或方法前后增加代码是相同的,只是这个工作交由编译器来完成。
第二种:EMIT反射,即:通过Emit反射动态生成代理类
第三种:普通反射+利用Remoting的远程访问对象时的直实代理类来实现
Spring如何选择用JDK还是CGLiB?
1)当Bean实现接口时,Spring就会用JDK的动态代理。
2)当Bean没有实现接口时,Spring使用CGlib是实现。
● 请问持久层设计要考虑的问题有哪些?请谈一下你用过的持久层框架都有哪些?
所谓"持久"就是将内存中的数据保存到关系型数据库、文件系统、消息队列等提供持久化支持的设备中。持久层就是系统中专注于实现数据持久化的相对独立的层面。
持久层设计的目标包括:
- 数据存储逻辑的分离,提供抽象化的数据访问接口。
- 数据访问底层实现的分离,可以在不修改代码的情况下切换底层实现。
- 资源管理和调度的分离,在数据访问层实现统一的资源调度(如缓存机制)。
- 数据抽象,提供更面向对象的数据操作。
● 请阐述一下实体对象的三种状态是什么?以及对应的转换关系是什么?
Hibernate文档中为Hibernate对象定义了四种状态,分别是:瞬时态(new, or transient)、持久态(managed, or persistent)、游状态(detached)和移除态
瞬时态:当new一个实体对象后,这个对象处于瞬时态,即这个对象只是一个保存临时数据的内存区域,如果没有变量引用这个对象,则会被JVM的垃圾回收机制回收。这个对象所保存的数据与数据库没有任何关系,除非通过Session的save()、saveOrUpdate()、persist()、merge()方法把瞬时态对象与数据库关联,并把数据插入或者更新到数据库,这个对象才转换为持久态对象。
持久态:持久态对象的实例在数据库中有对应的记录,并拥有一个持久化标识(ID)。对持久态对象进行delete操作后,数据库中对应的记录将被删除,那么持久态对象与数据库记录不再存在对应关系,持久态对象变成移除态(可以视为瞬时态)。持久态对象被修改变更后,不会马上同步到数据库,直到数据库事务提交。
游离态:当Session进行了close()、clear()、evict()或flush()后,实体对象从持久态变成游离态,对象虽然拥有持久和与数据库对应记录一致的标识值,但是因为对象已经从会话中清除掉,对象不在持久化管理之内,所以处于游离态(也叫脱管态)。游离态的对象与临时状态对象是十分相似的,只是它还含有持久化标识。
● 请说明一下锁机制的作用是什么?并且简述一下Hibernate的悲观锁和乐观锁机制是什么?
有些业务逻辑在执行过程中要求对数据进行排他性的访问,于是需要通过一些机制保证在此过程中数据被锁住不会被外界修改,这就是所谓的锁机制。
Hibernate支持悲观锁和乐观锁两种锁机制。悲观锁,顾名思义悲观的认为在数据处理过程中极有可能存在修改数据的并发事务(包括本系统的其他事务或来自外部系统的事务),于是将处理的数据设置为锁定状态。悲观锁必须依赖数据库本身的锁机制才能真正保证数据访问的排他性,乐观锁,顾名思义,对并发事务持乐观态度(认为对数据的并发操作不会经常性的发生),通过更加宽松的锁机制来解决由于悲观锁排他性的数据访问对系统性能造成的严重影响。
依赖注入的注入方式:
1、使用构造函数提供2、使用set方法提供3、使用注解提供
创建bean的方式:
1、使用默认构造函数2、使用普通工厂中的方法创建对象3、使用工厂中的静态方法创建对象
Aspectj对AOP的实现:
1)注册bean 2)配置aop 3)定义切入点 4)定义切面(哪种通知)
Spring如何解决循环依赖
spring中循环依赖有三种情况:
1、构造器注入形成的循环依赖。也就是beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成舒适化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。
2、setter注入构成的循环依赖。beanA需要在beanB的setter方法中完成初始化,beanB也需要在beanA的setter方法中完成初始化,spring设计的机制主要就是解决这种循环依赖
3、prototype作用域bean的循环依赖。这种循环依赖同样无法解决,因为spring不会缓存‘prototype’作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的。
spring只能解决setter注入构成的依赖,第二种情况中循环依赖的解决方案:
步骤一:beanA进行初始化,并且将自己进行初始化的状态记录下来,并提前向外暴露一个单例工程方法,从而使其他bean能引用到该bean
步骤二:beanA中有beanB的依赖,于是开始初始化beanB。
步骤三:初始化beanB的过程中又发现beanB依赖了beanA,于是又进行beanA的初始化,这时发现beanA已经在进行初始化了,程序发现了存在的循环依赖,然后通过步骤一中暴露的单例工程方法拿到beanA的引用(注意,此时的beanA只是完成了构造函数的注入但为完成其他步骤),从而beanB拿到beanA的引用,完成注入,完成了初始化,如此beanB的引用也就可以被beanA拿到,从而beanA也就完成了初始化。
Spring的加载过程
初始化环境—>加载配置文件—>实例化Bean—>调用Bean显示信息
2、springmvc
1.SpringMVC的流程
(1)用户发送请求至前端控制器DispatcherServlet
(2)Dispatcher Servlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler
(3)处理器映射器根据请求url获取具体的处理器,返回给DispatcherServlet
(4)DispatcherServlet调用HandlerAdapter处理器适配器
(5)处理器适配器经过适配调用具体的处理器
(6)Hander执行完成返回ModelAndView
(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析
(9)ViewResolver经过解析后返回具体View
(10)DispatcherServlet对View进行渲染视图
(11)DispatcherServlet响应用户
2.SpringMVC的主要组件?
(1)前端控制器DispatcherServlet(不需要程序员开发):接收请求、响应结果,相当于转发器,有
了DispatcherServlet就减少了其它组件之间的耦合度。
(2)处理器映射器HandlerMapping(不需要程序员开发):根据请求的url来查找Handler
(3)处理器适配器HandlerAdapter:在编写Handler的时候要按照HandlerAdapter要求的规则去编
写,这样适配器HandlerAdapter才可以正确的去执行Handler。
(4)处理器Handler(需要程序员开发)
(5)视图解析器ViewResolver(不需要程序员开发):进行视图的解析,根据视图逻辑名解析成真正
的视图。
(6)视图View(需要程序员开发jsp):View是一个接口,它的实现类支持不同的视图类型
3.SpringMVC和Struts2的区别?
(1)SpringMVC的入口是一个servlet即前端控制器,而Struts2入口是一个filter过滤器
(2)SpringMVC是基于方法开发的,传递参数到方法的形参上,Struts2是基于类开发的,传递参数是
通过类的属性。
4.SpringMVC怎么样设定重定向和转发的?
(1)转发:在返回值前面加forward,如forward:user.do?name=method4
(2)重定向:在返回值前面加redirect,如redirect:http://www.baidu.com
5.SpringMVC怎么和ajax相互调用
通过jackson框架就可以把java里面的对象直接转化为js可以识别的json对象,具体步骤如下:
(1)加入jackson.jar
(2)在配置文件中配置json的映射
(3)在接受Ajax方法里面可以直接返回Object,List等,但方法前面要加上@ResponseBody
6.如何解决post请求中乱码问题,get的又如何处理?
(1)解决post请求乱码问题
在web.xml中配置一个CharacterEncodingFilter过滤器,设置成utf-8;
(2)解决get请求乱码问题
1.修改tomcat配置文件添加编码与工程编码一致
2.对参数进行重新编码:
String userName= new String(request.getParameter("userName").getBytes("ISO8859-1"),"utf-8")
ISO8859-1是tomcat默认编码,需要将tomcat编码后的内容按utf-8编码。
7.SpringMVC的控制器是不是单例模式,如果是,有什么问题,怎么解决?
单例模式:单例模式类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
工厂模式
工厂模式提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能,解决方案是在控制
器里面不能写字段。
8.SpringMVC常用的注解有哪些?
@RequestMapping:用于处理请求url映射的注解,可用于类或方法上。用于类上,则表示类中的所有
响应请求的方法都是以该地址作为父路径。
@RequestBody:注解实现接受http请求的json数据,将json转换为java对象。
@ResponseBody:注解实现将Controller方法返回的对象转化为json对象响应给客户。
@RestController:注解相当于@Controller+@ResponseBody
@RequestParam:在controller的方法中参数名与表单中的参数名不一致,使用@RequestParm实现参
数绑定
@RequestParam(name="username") String t_username
@PathVariable:Controller除了可以接受表单提交的数据之外,还可以获取url中携带的变量,即路径
变量
9.如果在拦截请求中,我想拦截get方式提交的方法,怎么配置?
可以在@RequestMapping注解里面加上method=RequestMethod.GET
10.如果想在拦截的方法里面得到从前台传入的参数,怎么得到?
直接在形参里面声明这个参数就可以,但必须名字和传过来的参数一样。
11.SpringMVC中函数的返回值是什么?
返回值可以有多种类型,有String,void,和ModelAndView。
12.SpringMVC用什么对象从后台向前台传递数据?
通过ModelMap对象,可以在这个对象里面调用put方法,把对象加到里面,前台就可以通过el表达式
拿到。
13.注解原理
注解本质上是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们
通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对像调用自定义注解的方
***最终调用AnnotationInvocaitonHandle的invoke方法。该方***从memberValues这个Map中
索引出对应的值。而memberValues的来源是java常量池。
3、mybatis
一、什么是Mybatis?
- Mybatis是一个半ORM(对象关系映射),它内部封装了JDBC,开发时只需要关注SQL语句本身,不
需要再花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态
sql,可以严格控制sql执行性能,灵活度高。 - Mybatis可以使用XML或注解来配置和映射原生信息,将POJO映射成数据库中的记录,避免了几乎所
有的JDBC代码和手动设置参数以及获取结果集。
二、Mybatis的优点
1.基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML
里,解除sql与程序代码的耦合,便于统一管理。提供xml标签,支持编写动态SQL语句,并可重用。
2.与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接。
3.很好的与各种数据库兼容(因为Mybatis使用JDBC来连接数据库,所以只要JDBC支持的数据库
MyBatis都支持)。
4.能够与Spring很好的集成。
三、Mybatis框架的缺点:
1.SQL语句的编写工作量较大,尤其当字段多、关联表多时,编写SQL语句的功底有一定要求。
2.SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
四、Mybatis与Hibernate有哪些不同?
1.Mybatis和Hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句。
2.Mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,非常适合关系数据模型要求不高
的软件开发,因为这类软件需求变化频繁,一但需求变化要求迅速输出成功。
3.Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用hibernate
开发可以节省很多代码,提高效率。
四、#{}和${}的区别是什么?
{}是预编译处理,${}是字符串替换
Mybatis在处理#{}时会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
Mybatis在处理${}时,就是把¥{}替换成变量的值。
使用#{}可以有效的防止SQL注入,提高系统安全性。
五、当实体类中的属性名和表中的字段名不一样,怎么办?
1.定义字段名的别名,让字段名的别名和实体类的属性名一致。
2.通过来映射字段名和属性名一一对应
六、模糊查询like语句该怎么写?
1.在Java代码中添加sql通配符。
2.在sql语句中拼接通配符,会引起sql注入
七、通常一个XML映射文件,都会写一个Dao接口与之对应,请问这个Dao接口
的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?
1.Dao接口也即是Mapper接口,接口的全限名,就是映射文件中namespace的值;接口的方法名,就
是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
2.Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯
一定位一个MapperStatement。
八、Mybatis的XML映射文件中,不同的XML映射文件,id是否可以重复?
不同的XML映射文件,如果配置了namespace,那么id可以重复,如果没有配置namespace,那么id
不能重复。
原因是namespace+id是作为map的key使用的,如果没有namespace,就剩下id,那么,id重复会导
致数据互相覆盖。有了namespace,自然id就可以重复,namespace不同,namespace+id自然也就
不同。
九、为什么说Mybatis是半自动ORM映射工具?它与全自动的区别在哪里?
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对
象关系模型直接获取,所以它是全自动的。而Mybatis在查询关联对象或关联集合对象时,需要手动编
写sql来完成,所以,称之为半自动ORM映射工具。
十、MyBatis实现一对一有几种方式?具体怎么操作的?
有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次,通过resultMap里面配置
association节点配置一对一的类就可以完成。
嵌套查询是先查一个表,根据这个表里面的结果的外键id,再去另一个表里查询数据,也是通过
association配置,但另外一个表的查询通过select属性配置。
十二、使用MyBatis的mapper接口调用时有哪些要求?
1.Mapper接口方法名和mapper.xml中定义的每个sql的id相同。
2.Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。
3.Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
4.Mapper.xml文件中namespace即是mapper接口的类路径。
十三、mybatis的延迟加载
延迟加载:在真正使用数据时才发起查询,不用的时候不查询,按需加载
立即加载:不管用不用,只要一调用方法,马上发起查询
十四、Mybatis的一级缓存和二级缓存?
1)一级缓存 Mybatis的一级缓存是指SQLSession,一级缓存的作用域是SQlSession, Mybits默认开启一级缓存。 在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中取。 当执行SQL时候两次查询中间发生了增删改的操作,则SQLSession的缓存会被清空。 每次查询会先去缓存中找,如果找不到,再去数据库查询,然后把结果写到缓存中。 Mybatis的内部缓存使用一个HashMap,key为hashcode+statementId+sql语句。Value为查询出来的结果集映射成的java对象。 SqlSession执行insert、update、delete等操作commit后会清空该SQLSession缓存。
2)二级缓存是mapper级别的,Mybatis默认是没有开启二级缓存的。 第一次调用mapper下的SQL去查询用户的信息,查询到的信息会存放代该mapper对应的二级缓存区域。 第二次调用namespace下的mapper映射文件中,相同的sql去查询用户信息,会去对应的二级缓存内取结果。 如果调用相同namespace下的mapepr映射文件中增删改sql,并执行了commit操作
一级缓存:也称为本地缓存,用于保存用户在一次会话过程中查询的结果,用户一次会话中只能使用一个sqlSession,一级缓存是自动开启的,不允许关闭。
二级缓存:也称为全局缓存,是mapper级别的缓存,是针对一个表的查结果的存储,可以共享给所有针对这张表的查询的用户。也就是说对于mapper级别的缓存不同的sqlsession是可以共享的。
JDBC编程有哪些不足之处,Mybatis是如何解决这些问题的?
1) 数据库连接的创建、释放频繁造成系统资源浪费从而影响了性能,如果使用数据库连接池就可以解决这个问题。当然JDBC同样能够使用数据源。
解决:在SQLMapConfig.xml中配置数据连接池,使用数据库连接池管理数据库连接。
2) SQL语句在写代码中不容易维护,事件需求中SQL变化的可能性很大,SQL变动需要改变JAVA代码。解决:将SQL语句配置在mapper.xml文件中与java代码分离。
3) 向SQL语句传递参数麻烦,因为SQL语句的where条件不一定,可能多,也可能少,占位符需要和参数一一对应。解决:Mybatis自动将java对象映射到sql语句。
4) 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。解决:Mbatis自动将SQL执行结果映射到java对象。
Mybatis编程步骤 ?
Step1:创建SQLSessionFactory Step2:通过SQLSessionFactory创建SQLSession Step3:通过SQLSession执行数据库操作 Step4:调用session.commit()提交事物 Step5:调用session.close()关闭会话
MyBatis与hibernate有哪些不同 ?
1)Mybatis MyBatis 是支持定制化 SQL、存储过程以及高级映射的一种持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。Mybatis它不完全是一个ORM(对象关系映射)框架;它需要程序员自己编写部分SQL语句。 mybatis可以通过xml或者注解的方式灵活的配置要运行的SQL语句,并将java对象和SQL语句映射生成最终的执行的SQL,最后将SQL执行的结果在映射生成java对象。 Mybatis程序员可以直接的编写原生态的SQL语句,可以控制SQL执行性能,灵活度高,适合软件需求变换频繁的企业。 缺点:Mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件,则需要自定义多套SQL映射文件,工作量大。 2) Hibernate Hibernate是支持定制化 SQL、存储过程以及高级映射的一种持久层框架。 Hibernate对象-关系映射能力强,数据库的无关性好,Hirberate可以自动生成SQL语句,对于关系模型要求高的软件,如果用HIrbernate开发可以节省很多时间。
SQLMapConfig.xml中配置有哪些内容?
properties(属性) settings(配置) typeAliases(类型别名) typeHandlers(类型处理器) objectFactory(对象工厂) plugins(插件) environments(环境集合属性对象) environment(环境子属性对象) transactionManager(事务管理) dataSource(数据源) mappers(映射器)
Mybatis动态SQL?
传统的JDBC的方法,在组合SQL语句的时候需要去拼接,稍微不注意就会少少了一个空格,标点符号,都会导致系统错误。Mybatis的动态SQL就是为了解决这种问题而产生的;Mybatis的动态SQL语句值基于OGNL表达式的,方便在SQL语句中实现某些逻辑;可以使用标签组合成灵活的sql语句,提供开发的效率。
ORM:
对象关系映射(Object Relational Mapping,简称ORM),提供了概念性的、易于理解的模型化数据的方法,目的是想像操作对象一样操作数据库.因为数据库不是面向对象的,所以需要编程进行映射.
Mabatis三剑客分别是:mybatis-generator、mybatis-plugin、mybatis-pagehelper
4、配置文件总结
web.xml**
1.配置一个全局的参数:指定spring容器的配置文件applicationContext.xml路径。
2.编码过滤器。
3.配置***,创建spring容器对象。
4.前端控制器,指定配置文件spring-mvc.xml路径
spring-mvc.xml
1.扫描包,创建类对象。
2.视图解析器。
3.注解驱动。
4.自定义类型转换器。
5.文件上传。
6.拦截器。
7.静态资源放行。
applicationContext.xml
持久层配置
1.引入数据库外部属性文件。
2.创建数据源对象。
3.创建sqlSessionFactory对象:以前在测试类中使用构建者模式创建SessionFactory对象,引入
SqlMapConfig.xml文件。
4.扫描dao层接口的包,创建动态代理对象,存入spring容器中。
业务层配置
5.扫描包,创建业务层所有类对象。
6.声明式事务
1)事务管理类对象
2)事务增强对象
3)aop配置:切面配置
在使用springmvc框架的时候,在处理json的时候需要用到spring框架特有的注解@ResponseBody或
者@RestController注解,这两个注解都会处理返回的数据格式,使用了该类型注解后返回的不再是视
图,不会进行转跳,而是返回json或xml数据格式,输出在页面上。
那么,这两个注解在使用上有什么区别呢?
@ResponseBody,一般是使用在单独的方法上的,需要哪个方法返回json数据格式,就在哪个方法
上使用,具有针对性。
@RestController,一般是使用在类上的,它表示的意思其实就是结合了@Controller和
@ResponseBody两个注解,
如果哪个类下的所有方法需要返回json数据格式的,就在哪个类上使用该注解,具有统一性;需要注意
的是,使用了@RestController注解之后,其本质相当于在该类的所有方法上都统一使用了
@ResponseBody注解,所以该类下的所有方法都会返回json数据格式,输出在页面上,而不会再返回
视图。
4、spring cloud
SpringCloud将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。
1、组件
1、Eureka:注册中心
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。
同时,服务提供方与Eureka之间通过“心跳”
机制进行监控,当某个服务提供方出现问题,Eureka自然会把它从服务列表中剔除。这就实现了服务的自动注册、发现、状态监控。
基本架构:
- Eureka:就是服务注册中心(可以是一个集群),对外暴露自己的地址
- 提供者:启动后向Eureka注册自己信息(地址,提供什么服务)
- 消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
- 心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
Eureka架构中的三个核心角色:
服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-demo
服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的user-service-demo
服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实现的consumer-demo
2、Zuul:服务网关
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制
等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。在微服务架构中,Zuul就是守门的大Boss!一夫当关,万夫莫开!
不管是来自于客户端(PC或移动端)的请求,还是服务内部调用。一切对服务的请求都会经过Zuul这个网关,然后再由网关来实现 鉴权、动态路由等等操作。Zuul就是我们服务的统一入口。
Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了
3、Ribbon:负载均衡
负载均衡是我们处理高并发、缓解网络压力和进行服务器扩容的重要手段之一。但是,一般情况下我们
所说的负载均衡通常都是指服务器端负载均衡,服务器端负载均衡又分为两种:一种是硬件负载均衡,
还有一种是软件负载均衡
4、Feign:服务调用
Feign可以把Rest的请求进行隐藏,伪装成类似SpringMVC的Controller一样。你不用再自己拼接url,拼接参数等等操作,一切都交给Feign去做。
Feign中本身已经集成了Ribbon依赖和自动配置、Feign默认也有对Hystix的集成、Spring Cloud Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗。
5、Hystix:熔断器
雪崩效应:
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不
可用的情况,这种现象被称为雪崩效应。雪崩效应是一种因服务提供者的不可用导致服务消费者 的不可
用,并将不可用逐渐放大的过程。
A作为服务提供者,B为A的服务消费者,C和D是B的服务消费者,A不可用引起了B的不可用,并将不可
用像滚雪球一样放大到C和D时,雪崩效应就形成了。
Hystix是Netflix开源的一个延迟和容错库,用于隔离访问远程服务、第三方库,防止出现级联失败。当有服务出现异常时,直接进行失败回滚,服务降级处理:
当服务繁忙时,如果服务出现异常,不是粗暴的直接报错,而是返回一个友好的提示,虽然拒绝了用户的访问,但是会返回一个结果。
这就好比去买鱼,平常超市买鱼会额外赠送杀鱼的服务。等到逢年过节,超时繁忙时,可能就不提供杀鱼服务了,这就是服务的降级。
系统特别繁忙时,一些次要服务暂时中断,优先保证主要服务的畅通,一切资源优先让给主要服务来使用,在双十一、618时,京东天猫都会采用这样的策略。
使用步骤:导入依赖--->Feign的客户端--->开启feign的功能--->启动测试
2、FastDFS
3、单点登录
单点登录又叫做sso,是在互相信任的多个系统中,只需要输入一次用户名密码,就可以直接登录其它
互相信任的系统。
使用场景:
传统企业项目:作系统权限集成
互联网项目:soa分布式架构下,是多个项目,如果跨项目跳转访问能够自动认证。
单点登录服务器,项目中配置cas的客户端工具包,就可以不用写代码实现单点登录。
cas和springSecurity整合到品优购项目中:整合前:
springSecurity的作用:
认证:判断用户名和密码是否正确
赋权:用户登录后,应该具有什么样的访问权限
整合后:
springSecurity:
赋权,用户登录后,应该具有什么样的访问权限,用户的认证工作交给cas,因为cas更擅长。
cas:判断用户名密码是否正确,cas更擅长认证工作,因为它能完成在多个互相信任的系统中,只要在
一个系统中登录后,在其它系统中就不需要再次输入用户名密码就能够自动认证。
4、什么是集群?
集群就是多台机器,是一种线上的部署方案,很多机器加起来,性能就比一台机器强,一般用这种部署
方案来解决高并发,高可用,容灾,集群也有不同的叫法,负载均衡集群,高可用集群,扩容集群等。
什么是分布式
分布式也叫做SOA,是一种设计方案,以前使用所有模块在一个项目中的写法,叫做垂直架构,后来由
于互联网的兴起,为了模块间的解耦和扩展性以及部署的灵活性,会将一个项目按照模块进行拆分,一
个模块就是一个项目这种设计方案叫做分布式架构,也叫做SOA架构。
什么是负载均衡器以及作用?
负载均衡器就是为了解决高并发而生的一种解决方案,它的作用就是接收所有请求,并将请求分发给
tomcat集群,这样均匀分布请求到很多tomcat中,可以解决高并发。
负载均衡器的分类:
硬负载:硬负载就是硬件,很贵,需要花钱购买,常用的硬负载机器有f5,netsclaer。优点就是性能好
软负载:软件,***,需要部署在linux操作系统上,常用的有nginx,lvs优点就是***,缺点
就是性能没有硬负载好,nginx一般单机可以抗住每秒5万的请求量。
nginx反向代理配置:
一个nginx充当代理人的角色,而后面的tomcat不是集群就是单台tomcat部署的我们一个项目,这种部
署方案nginx就相当于我们项目的代理人,叫做反向代理配置。
nginx负载均衡配置:
一个nginx,对应tomcat集群,也就是多个tomcat,这多个tomcat中部署的是同一个项目,nginx就可
以将请求均匀的分发给tomcat集群来处理请求,这种配置叫做负载均衡配置。
5、什么是docker?
docker是一种容器化技术,也可以说是一种虚拟化技术。通俗的理解就是一个高性能,Linux服务器上
才能用的虚拟机软件。docker跟vmware虚拟机区别:
docker:
Linux服务器上才可以用,高性能,docker虚拟出来的虚拟机只能是linux系统
vmware:
window版虚拟机软件,低性能,vmware可以虚拟出windows,linux,centos,unix等系统。
我们在企业用docker做什么用?
使用docker进行部署,降低企业运营部署成本,基本实现零成本部署。
docker解决了部署的时候,同一台机器软件版本差异化造成的冲突问题。
docker对于硬件资源的管理,非常擅长,能更好的发挥每一台机器的性能。
spring cloud和rpc框架的区别
Dubbo RPC:基于TCP或HTTP的远程过程调用(就像在本地调用一样),RPC强调的是远程调用。
spring cloud:基于springboot,而springboot是基于HTTP协议REST风格的RPC。
对比:
1、协议:服务间通信协议不同,Dubbo是基于TCP协议的rpc,spring cloud基于http协议。
2、RPC避免了上面提到的原生RPC带来的问题。REST相比RPC更为灵活,SpringCloud不存在代码级别的强依赖
3、效率:由于协议的不同,调用的效率相比之下Dubbo比SpringCLoud效率高。
4、技术开放性:SpringCLoud基于http协议,由于http的协议更方便于多语言情况下的整合,提供服务方可以用任意语言开发服务。
5、spring cloud是微服务生态,包括完整的微服务相关的组件工具集,而RPC是远程调用的技术,仅仅是微服务的一部分,而dubbo框架正是RPC的实现框架。
6、Spring Cloud还提供了包括Netflix Eureka、hystrix、feign、Spring Boot Admin 、Sleuth、config、stream、security、sleuth等分布式服务解决方案,而Dubbo为了拥抱融入Spring Cloud生态,Dubbo也在积极规划和演进适配SpringCloud生态的新版本。
5、spring boot
一、热部署
热部署是指当我们修改代码后,服务能自动加载新修改的内容,这样大大提高了 我们开发的效率,否则
每次都要手动重启,这样就比较耗时。
二、使用lombok
在之前编写的代码中,我们写了很多bean,这里面加了很多set、get方法,这些方法冗余,但却也不可
缺少。可以使用lombok包,通过该工具,就不用在bean源码中手动添加set、get方法了,除此之外
equals、hashcode、toString方法也无需手动在源码中添加了。
三、什么是SpringBoot?
SpringBoot是Spring开源组织下的子项目,是Spring组件一站式解决方案,主要是简化了使用Spring的
难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。
四、SpringBoot有哪些有哪些优点?
1.减少开发,测试时间和努力
2.使用JavaConfig有助于避免使用XML
3.避免大量的Maven导入和各种版本冲突。
五、SpringBoot的核心配置文件有哪几个?它们的区别是什么?
SpringBoot的核心配置文件是application和bootstrap配置文件
application:主要用于Spring Boot项目的自动化配置。
@Controller
@ResponseBody
@RestController
六、SpringBoot的配置文件有哪几种格式?它们有什么区别?
.properties和.yml,它们的区别主要是书写格式不同。
七、Spring Boot的核心注解是哪个?它主要由哪几个注解组成的?
@SpringBootApplication是SpringBoot的核心注解,主要组合包含了以下3个注解:
1.@SpringBootConfiguration:组合了@Configuration注解,实现配置文件的功能。
2.@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据
源自动配置功能:@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
3.@ComponentScan:Spring组件扫描
八、开启Spring Boot特性有哪几种方式?
1.继承spring-boot-start-parent项目
2.导入spring-boot-dependencies项目依赖
九、Spring Boot需要独立的容器运行吗?
可以不需要,内置了Tomcat/Jetty等容器。
十、运行SpringBoot有哪几种方式?
1、打包用命令或者放到容器中运行。
2、用Maven/Gradle插件运行。
3.直接执行main方法。
十一、SpringBoot自动配置原理是什么?
注解@EnableAutoConfiguration,@Configuration,@ConditionalOnClass就是自动配置的核心,首先
它得是一个配置文件,其次根据类路径下是否有这个类去自动配置。
十二、SpringBoot实现分页和排序?
使用Spring Data-JPA可以实现将可分页的org.springframework.data.domain.Pageable传递给存储库
方法。
十三、如何实现Spring Boot应用程序的安全性?
使用spring-boot-starter-security依赖项,并且必须添加安全配置。
十四、如何集成Spring Boot和ActiveMQ?
使用spring-boot-start-activemq依赖关系。它只需要很少的配置,并且不需要样板代码。
十五、SpringBoot中的监视器是什么?
Spring boot actuator是spring启动框架中的重要功能之一。spring boot监视器可帮助您访问生产环境
中正在运行 的应用程序的当前状态。
十六、什么是Swagger?你用Spring Boot实现了它吗?
Swagger是用于生成Restful Web服务的可视化表示的工具,规范和完整框架实现。它使文档能够以与
服务器相同的速度更新。
十七、如何使用Spring Boot实现异常处理?
Spring提供了一种使用ControllerAdvice处理异常的非常有用的方法。我们通过实现一个
ControlerAdvice类,来处理控制器类抛出的所有异常。
十八、RequestMapping和GetMapping的不同之处在哪里?
RequestMapping具有类属性的,可以进行GET\POST\PUT或者其它的注释中具有的请求方法
GetMapping是GET请求方法中的一个特例。它只是RequestMapping的一个延伸,目的是为了提高清
晰度。
十九、Spring Boot可以兼容老Spring项目吗?如何做?
可以兼容,使用@ImportResource注解导入老Spring项目配置文件。
二十、包含Spring boot应用有哪些方法?
在生产中使用Https使用Snyk检查你的依赖关系
升级到最新版本
启用CSRF保护
使用内容安全策略防止XSS攻击。
1.关于缓存
缓存和CDN:CDN是内容分发网络,其实,可以把它看做是一个内容缓存服务器,不同的运营商,自己使用的CDN(缓存策略)是不一样的。我们将访问的资源存放在离我们最近的CDN服务器上,通过HTTP协议中的cache-contol:max-age来设置缓存时间。当我们下次访问同一资源的时候,通过判断缓存数据是否过期来判断是否重新向源站发出请求。
3.断点续传以及多线程下载
请求时设置了一个请求头range的范围,服务器响应Accept-Range字段表示是否接受断点续传,content-Range返回接受的范围即文件大小,进行判断后,返回请求的范围数据,及响应码。
6、计算机网络
计算机网络七层模型
应用层(数据)、表示层(数据)、会话层(数据)、传输层(数据报文)、网络层 (数据分组)、数据链路层 (帧)、物理层 (比特)
TCP/IP四层模型
应用层 :应用进程 ->文件传输协议(FTP)、域名服务(DNS)、超文本传输协议(HTTP)
传输层:TCP /UDP
网络层:ICMP/IGMP/ARP/RARP/IP 网络协议 IP
网络接口层:网络接口
1.Http和Https的区别
Http协议运行在TCP之上,明文传输,客户端和服务器都无法验证对方的身份;Https运行于SSL之上,SSL运行于TCP之上,是添加了加密和认证机制的Http。
端口不同:http和https使用不同的连接方式,用的端口也不一样,前者是80端口,后者是443端口;
资源消耗不同:和http通信相比,https通信会由于加减密处理消耗更多的CPU和内存资源;
开销:https通信需要证书,而证书一般需要向认证机构购买。
https的加密机制是一种共享密钥加密和公开加密并用的混合加密机制。
2.对称加密与非对称加密
对称加密是指加密和解密使用同一个密钥的方式,这种方式存在的最大的问题就是密钥发送问题,即如
何安全的将密钥发给对方;而非对称加密是指使用一对非对称密钥,即公钥和私钥,公钥可以随意发
布,但私钥只有自己知道。发送密文的一方使用对方的公钥进行加密处理,对方接收到加密信息,使用
自己的私钥进行解密。
3.三次握手和四次挥手
tcp的几个状态:
SYN表示建立连接, FIN表示关闭连接, ACK表示响应, PSH表示有数据传输, RST表示连接重置!
(1)三次握手(我要和你建立连接,你真的要和我建立连接么,我真的要和你建立连接,成功):
1.第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client
进入syn_sent状态,等待Server确认。
2.第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和
ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进
入syn_rcvd状态。
3.第三次握手:Client收到确认后,检查ack=J+1,ACK是否为1,如果正确则将标志位ACK为1,
ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建
立成功,Client和Server进入established状态,完成三次握手,随后Client和Server之间可以开始传输
数据了。
(2)四次挥手(我要和你断开连接;好的,断吧。我也要和你断开连接;好的,断吧)
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入fin_wait_1状态。
第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个
FIN占用一个序号),Server进入Close_wait状态。此时TCP连接处于半关闭状态,即客户端已经没有要
发送的数据了,但服务端若发送数据,则客户端仍要接收。
第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入Last_ack状态。
第四次挥手:Client收到FIN后,Client进入Time_wait状态,接着发送一个ACK给Server,确认序号为
收到序号+1,Server进入Closed状态,完成四次挥手。
4.域名系统(服务)协议(DNS)是一种分布式网络目录服务,主要用于域名与 IP 地址的相互转换,以及控制因特网的电子邮件的发送。
5.子网掩码:是一种用来指明一个IP地址的哪些位标识的是主机所在的子网,以及哪些位标识的是主机的位掩码,子网掩码只有一个作用,就是将某个IP地址划分成网络地址和主机地址两部分
6.网关:网关又称网间连接器、协议转换器。网关在网络层以上实现网络互连,是复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关既可以用于广域网互连,也可以用于局域网互连。 网关是一种充当转换重任的计算机系统或设备。
7、TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP对系统资源要求较多,UDP对系统资源要求较少。
7、Java基础
● int和Integer有什么区别?
为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型(wrapper class),int的包装类就是Integer,从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 提供两种不同的类型:引用类型和原始类型(或内置类型)。Int是java的原始数据类型,Integer是java为int提供的封装类。
- 原始类型: boolean,char,byte,short,int,long,float,double
- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
请你谈谈大O符号(big-O notation)并给出不同数据结构的例子
大O符号描述了当数据结构里面的元素增加的时候,算法的规模或者是性能在最坏的场景下有多么好。
大O符号也可用来描述其他的行为,比如:内存消耗。因为集合类实际上是数据结构,我们一般使用大O符号基于时间,内存和性能来选择最好的实现。大O符号表示一个程序运行时所需要的渐进时间复杂度上界。
● 请你解释什么是值传递和引用传递?
值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象.一般认为java内的传递都是值传递.
请你说说Lamda表达式的优缺点。
优点:1. 简洁。2. 非常容易并行计算。3. 可能代表未来的编程趋势。
缺点:1. 若不用并行计算,很多时候计算速度没有比传统的 for 循环快。(并行计算有时需要预热才显示出效率优势)2. 不容易调试。3. 若其他程序员没有学过 lambda 表达式,代码不容易让其他语言的程序员看懂。
● 你知道java8的新特性吗,请简单介绍一下
Lambda 表达式 − Lambda允许把函数作为一个方法的参数
方法引用− 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法− 默认方法就是一个在接口里面有了一个实现的方法。
新工具− 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
==与equlas有什么区别?
==
- 如果是基本类型,判断它们值是否相等;
- 如果是引用对象,判断两个对象指向的内存地址是否相同。
equals
- 如果是字符串,表示判断字符串内容是否相同;
- 如果是object对象的方法,比较的也是引用的内存地址值;
- 如果自己的类重写equals方法,可以自定义两个对象是否相等。
final关键字
当用final修饰一个类时,表明这个类不能被继承。“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。
对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
● 接口和抽象类的区别是什么?
Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:
接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。
类可以实现很多个接口,但是只能继承一个抽象类
类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。
抽象类可以在不提供接口方法实现的情况下实现接口。
Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。
接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,如果它包含main方法的话是可以被调用的。
● 请你说说Iterator和ListIterator的区别?
Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
ListIterator实现了Iterator接口,并包含其他的功能:增加元素,替换元素,获取前一个和后一个元素的索引
● 请问什么是java序列化?以及如何实现java序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
java内存模型(Java Memory Model)是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。可以避免像c++等直接使用物理硬件和操作系统的内存模型在不同操作系统和硬件平台下表现不同,比如有些c/c++程序可能在windows平台运行正常,而在linux平台却运行有问题。
8、web
● 请谈一谈JSP有哪些内置对象?以及这些对象的作用分别是什么?
JSP有9个内置对象:
- request:封装客户端的请求,其中包含来自GET或POST请求的参数;
- response:封装服务器对客户端的响应;
- pageContext:通过该对象可以获取其他对象;
- session:封装用户会话的对象;
- application:封装服务器运行环境的对象;
- out:输出服务器响应的输出流对象;
- config:Web应用的配置对象;
- page:JSP页面本身(相当于Java程序中的this);
- exception:封装页面抛出异常的对象。
● 请简要说明一下JSP和Servlet有哪些相同点和不同点?另外他们之间的联系又是什么呢?
JSP 是Servlet技术的扩展,本质上是Servlet的简易方式,更强调应用的外表表达。JSP编译后是”类servlet”。Servlet和JSP最主要的不同点在于,Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。JSP侧重于视图,Servlet主要用于控制逻辑
Netty和Tomcat
最大的区别就在于通信协议,Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器,但是Netty不一样,他能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解码字节流,完成类似redis访问的功能,这就是netty和tomcat最大的不同。
● 请谈谈你对Javaweb开发中的***的理解?
***模型涉及以下三个对象,
(1)事件:用户对组件的一个操作,称之为一个事件
(2)事件源:发生事件的组件就是事件源
(3)事件***(处理器):监听并负责处理事件的方法
执行顺序:
1、给事件源注册***
2、组件接受外部作用,也就是事件被触发
3、组件产生一个相应的事件对象,并把此对象传递给与之关联的事件处理器
4、事件处理器启动,并执行相关的代码来处理该事件。
● 请问过滤器有哪些作用?以及过滤器的用法又是什么呢?
过滤器(filter)是从Servlet 2.3规范开始增加的功能,对Web应用来说,过滤器是一个驻留在服务器端的Web组件,它可以截取客户端和服务器之间的请求与响应信息,并对这些信息进行过滤。当Web容器接受到一个对资源的请求时,它将判断是否有过滤器与这个资源相关联。如果有,那么容器将把请求交给过滤器进行处理。在过滤器中,你可以改变请求的内容,或者重新设置请求的报头信息,然后再将请求发送给目标资源。
● 请回答一下servlet的生命周期是什么。servlet是否为单例以及原因是什么?
Servlet 通过调用 init () 方法进行初始化。
Servlet 调用 service() 方法来处理客户端的请求。
Servlet 通过调用 destroy() 方法终止(结束)。
最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
Servlet单实例,减少了产生servlet的开销;
● 请你说说,cookie 和 session 的区别?
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
2、cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session。
3、session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用COOKIE。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
● 说说你对get和post请求,并且说说它们之间的区别?
①get请求用来从服务器上获得资源,而post是用来向服务器提交数据;
②get将表单中数据按照name=value的形式,添加到action 所指向的URL 后面,并且两者使用"?"连接,而各个变量之间使用"&"连接;post是将表单中的数据放在HTTP协议的请求头或消息体中,传递到action所指向URL;
③get传输的数据要受到URL长度限制(1024字节);post可以传输大量的数据,上传文件通常要使用post方式;
④使用get时参数会显示在地址栏上,如果这些数据不是敏感数据可以使用get;对于敏感数据还是应用使用post;
● 请谈谈,转发和重定向之间的区别?
forward是容器中控制权的转向,是服务器请求资源,服务器直接访问目标地址的URL,把那个URL 的响应内容读取过来,然后把这些内容再发给浏览器,浏览器根本不知道服务器发送的内容是从哪儿来的,所以它的地址栏中还是原来的地址。
redirect就是服务器端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,因此从浏览器的地址栏中可以看到跳转后的链接地址,很明显redirect无法访问到服务器保护起来资源,但是可以从一个网站redirect到其他网站。forward更加高效,在有些情况下,比如需要访问一个其它服务器上的资源,则必须使用重定向
解决session共享问题
方法一、使用Nginx让它绑定ip,配置Nginx。
方法二、使用spring session+redis的方法解决session共享问题
9、电商项目
1、跨域?
当一个资源去访问另一个不同域名或者同域名不同端口的资源时,就会发出跨域请求。如果此时另一个资源不允许其进行跨域资源访问,那么访问的那个资源就会遇到跨域问题。
因为跨域问题是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同域名的路径,这能有效的阻止跨站攻击。
2、异步查询工具axios
异步查询数据,自然是通过ajax查询,大家首先想起的肯定是jQuery。但jQuery与MVVM的思想不吻合
3、解决跨域问题的方案
Jsonp:最早的解决方案,利用script标签可以跨域的原理实现。缺点:需要服务的支持、只能发起GET请求
nginx反向代理:利用nginx反向代理把跨域为不跨域,支持各种请求方式。缺点:需要在nginx进行额外配置
CORS(跨域资源共享):规范化的跨域请求解决方案,安全可靠。
优势:在服务端进行控制是否允许跨域,可自定义规则、支持各种请求方式
缺点:会产生额外的请求
同源策略
是浏览器的安全策略。是一种约定,是浏览器最核心最基本的安全功能。如果没有同源策略,浏览器很容易收到XSS,CSRF攻击。保证用户信息安全,防止恶意网站窃取数据
同源指“协议+域名(主机)+端口”三者相同。任一不同,都属于非同源。即使不同域名对应同一IP地址也非同源。
4、服务治理(SOA)
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
服务治理要做什么?
- 服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
- 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
- 动态监控服务状态监控报告,人为控制服务状态
5、远程调用方式
无论是微服务还是SOA,都面临着服务间的远程调用。常见的远程调用方式有以下几种:
RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型
Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。
现在热门的Rest风格,就可以通过http协议来实现。
微服务,更加强调的是独立、自治、灵活。而RPC方式的限制较多,因此微服务框架中,一般都会采用基于Http的Rest风格服务。
6、Nginx
nginx可以作为web服务器,但更多的时候,我们把它作为网关,因为它具备网关必备的功能:
反向代理、负载均衡、动态路由、请求过滤。
Web服务器分2类:
- web应用服务器,如:tomcat、resin、jetty
- web服务器,如:Apache 服务器、Nginx、IIS
区分:web服务器不能解析jsp等页面,只能处理js、css、html等静态资源。
并发:web服务器的并发能力远高于web应用服务器。
nginx作为反向代理
什么是反向代理?
- 代理:通过客户机的配置,实现让一台代理服务器代理客户机,客户的所有请求都交给代理服务器
- 反向代理:用一台服务器,代理真实服务器,用户访问时,不再是访问真实服务器,而是代理服务器。
nginx可以当做反向代理服务器来使用:
- 我们需要提前在nginx中配置好反向代理的规则,不同的请求,交给不同的真实服务器处理
- 当请求到达nginx,nginx会根据已经定义的规则进行请求的转发,从而实现路由功能
- 利用反向代理,就可以解决我们前面所说的端口问题
10、数据结构与算法
1、哈希
HashMap的底层实现
- 底层由链表+数组实现
- 可以存储null键和null值
- 线性不安全
- 初始容量为16,扩容每次都是2的n次幂(保证位运算)
- 加载因子为0.75,当Map中元素总数超过Entry数组的0.75,触发扩容操作.
- 并发情况下,HashMap进行put操作会引起死循环,导致CPU利用率接近100%
HashMap底层是数组和链表的结合。HashMap通过key的HashCode经过扰动函数处理过后得到Hash
值,然后通过位运算判断当前元素存放的位置,如果当前位置存在元素的话,就判断该元素与要存入的元素的hash值以及key是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。当Map中的元素总数超过Entry数组的0.75时,触发扩容操作,为了减少链表长度,元素分配更均匀。
HashMap基于哈希思想,实现对数据的读写。当我们将键值对传递给put()方法时,它调用 键对象的hashCode()方法来计算hashcode,然后后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表 来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。HashMap在每个 链表节点中储存键值对对象。当两个不同的键对象的hashcode相同时,它们会储存在同一个 bucket位置的链表中,可通过键对象的equals()方法用来找到键值对。如果链表大小超过阈 值( 8),链表就会被改造为树形结构。
————————————————rehash解决多次扩容后数据分配问题
JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。 (一个是链表的长度达到8个,一个是数组的长度达到64个)
那为什么选择8才会选择使用红黑树呢
为了配合使用分布良好的hashCode,树节点很少使用。并且在理想状态下,受随机分布的hashCode影响,链表中的节点遵循泊松分布,而且根据统计,链表中节点数是8的概率已经接近千分之一,而且此时链表的性能已经很差了。所以在这种比较罕见和极端的情况下,才会把链表转变为红黑树。因为链表转换为红黑树也是需要消耗性能的,特殊情况特殊处理,为了挽回性能,权衡之下,才使用红黑树,提高性能。也就是大部分情况下,hashmap还是使用的链表,如果是理想的均匀分布,节点数不到8,hashmap就自动扩容
哈希冲突:如果两个不同对象的hashCode相同,这种现象称为hash冲突。
有以下的方式可以解决哈希冲突:
- 开放定址法
- 再哈希法
- 链地址法
- 建立公共溢出区
● 请你说明HashMap和Hashtable的区别?
HashMap和Hashtable都实现了Map接口,因此很多特性非常相似。但是,他们有以下不同点:
HashMap允许键和值是null,而Hashtable不允许键或者值是null。
Hashtable是同步的,而HashMap不是。HashMap更适合于单线程环境,而Hashtable适合于多线程环境。
HashMap提供了可供应用迭代的键的集合,因此,HashMap是快速失败的。
HashMap和TreeMap的区别
HashMap:数组方式存储key/value,线程非安全,允许null作为key和value,key不可以重复,value
允许重复,不保证元素迭代顺序是按照插入时的顺序,key的hash值是先计算key的hashcode值,然后
再进行计算,每次扩容会重新计算key的hash值,会消耗资源,要求key必须重写equals和hashcode方
法。它默认初始容量为16,加载因子0.75,扩容为旧容量的2倍,查找元素快,如果key一样则比较value,
如果value不一样,则按照链表结构存储value,就是一个key后面有多个value
TreeMap:基于红黑树的NavigableMap实现,线程非安全,不允许null,key不可以重复,value允许重
复,存入TreeMap的元素应当实现Comparable接口或者实现Comparator接口,会按照排序后的顺序
迭代元素,两个相比较key不得抛出classCastException。主要用于存入元素的时候对元素进行自动排
序,迭代输出的时候就按照排序顺序输出。
● 请你说明一下TreeMap的底层实现?
TreeMap 的实现就是红黑树数据结构,一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点。
红黑树的插入、删除、遍历时间复杂度都为O(lgN),所以性能上低于哈希表。但是哈希表无法提供键值对的有序输出,红黑树因为是排序插入的,可以按照键的值的大小有序输出。红黑树性质:
2、树
红黑树的性质:
1.节点是红色或黑色。
2.根节点是黑色。
3.每个叶子节点都是黑色的空节点(NIL节点)。
4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
平衡二叉树的性质:
它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。
区别:
1、红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,保证每次插入最多只需要三次旋转就能达到平衡,实现起来也更为简单。
2、平衡二叉树追求绝对平衡,条件苛刻,实现起来比较麻烦,每次插入新节点之后需要旋转的次数不能预知。
4、链表
● 请说明ArrayList和LinkedList的区别?
ArrayList和LinkedList都实现了List接口,他们有以下的不同点:
ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度对元素进行随机访问。与此对应,LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
相对于ArrayList,LinkedList的插入,添加,删除操作速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。
LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
● 请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
Array大小是固定的,ArrayList的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
5、数组
6、排序
7、堆与栈
8、队列
9、高级算法
11、分布式
分布式锁的几种常用实现方式
分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性
针对分布式锁的实现,目前比较常用的有以下几种方案:
基于数据库实现分布式锁:核心思想是在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
基于缓存实现分布式锁:实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
基于Zookeeper实现分布式锁
ZooKeeper是一个为分布式应用提供一致***的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名。(1)创建一个目录mylock;
(2)线程A想获取锁就在mylock目录下创建临时顺序节点;
(3)获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,获得锁;
(4)线程B获取所有节点,判断自己不是最小节点,设置监听比自己次小的节点;
(5)线程A处理完,删除自己的节点,线程B监听到变更事件,判断自己是不是最小的节点,如果是则获得锁。
什么是分布式文件系统:
1、传统文件系统管理的文件就存储在本机。
2、分布式文件系统管理的文件存储在很多机器,这些机器通过网络连接,要被统一管理。无论是上传
或是访问文件,都需要通过管理中心来访问。
功能丰富:
文件存储、文件同步、文件访问、存取负载均衡、在线扩容
适合有大容量存储需求的应用或系统。
FastDFS两个主要的角色:Tracker Server和Storage Server
Tracker Server:跟踪服务器,主要负责调度storage节点与client通信,在访问上起负载均衡的作用,
和记录storage节点的允许状态,是连接client和storage节点的枢纽。
Storage Server:存储服务器,保存文件和文件的meta data(元数据),每个storage server会启动一
个单独的线程主动向Tracker cluster中每个tracker server报告其状态信息,包括磁盘使用情况,文件同
步情况及文件上传下载次数统计等信息。
Group:文件组,多台Strage Server的集群。上传一个文件到同组内的一台机器上后,FastDFS会将该
文件即时同步到同组内的其它所有机器上,起到备份的作用。不同组的服务器,保存的数据不同,而且
相互独立,不进行通信。
Tracker Cluster:跟踪服务器的集群,有一组Tracker Server(跟踪服务器)组成。
Storage Cluster:存储集群,有多个Group组成。
FastDFS上传和下载流程
上传:
1.Client通过Tracker server查找可用的Storage server。
2.Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
3.Client通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传。
4.上传完成,Storage server返回Client一个文件ID,文件上传结束。
下载:
1.Client通过Trackerserver查找要下载文件所在的Storage server。
2.Tracker server向Client返回包含指定文件的某个Storage server的IP地址和端口号。
3.Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并指定要下载
文件。
4.下载文件成功。
为什么要使用RPC?组成部分?
在一个典型RPC的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,而RPC的主要目标是更容易地构建分布式应用。为实现该目标,RPC框架需提供一种透明调用机制,让使用者不必显式的区分本地调用和远程调用。
1 、服务寻址2 、序列化和反序列化3 、网络传输
12、Linux
部署项目用到的linux命令:
1.进入tomcat的bin目录 cd /data/apache-tomcat-6.0.39/bin
2.查看tomcat的进程 ps -ef | grep tomcat
3.杀死进程 kill -9 + 进程数
查看进程 2.1、ps -ef | grep xx 2.2、ps -aux | grep xxx(-aux显示所有状态)
查看端口:1、netstat -anp | grep 端口号(状态为LISTEN表示被占用)
4.启动项目 sh startup.sh
5.永久删除文件 rm -rf 文件
6.复制文件 cp -Rf 原路径/ 目的路径/
7.压缩文件夹
解压:tar zxvf FileName.tar.gz
压缩:tar zcvf FileName.tar.gz DirName
8.解压(安装zip命令)* unzip 压缩包
9.移动 mv +路径/文件 +要移到的路径
9.从本机复制文件到远程 scp -r ROOT root@192.168.1.1:/data/apache-tomcat-6.0.39/webapps
scp -r 目录名 远程计算机用户名@远程计算机的ip:远程计算机存放该目录的路径
13、并发编程
进程与线程的区别:
1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间, 而线程是共享进程中的数据、地址空间
3、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。
4、多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
● 如何保证线程安全?
通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源
● 线程同步和线程调度的相关方法。
- wait():使一个线程处于等待状态,并且释放所持有的对象的锁;
- sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理异常;
- notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
- notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
*● Java中有几种方法可以实现一个线程?用什么关键字修饰同步方法? *
有四种实现方法,分别是继承Thread类与实现Runnable接口,使用Callable和Future创建线程、使用线程池例如用Executor框架。
● 启动一个线程是用run()还是start()?
启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。
● 请说明一下sleep() 和 wait() 有什么区别?
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
● 请分析一下同步方法和同步代码块的区别是什么?
区别:同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,可以选择只同步会发生同步问题的部分代码而不是整个方法。
● 请详细描述一下线程从创建到死亡的几种状态都有哪些?
新建( new ):新创建了一个线程对象。
可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权 。
运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,阻塞的情况分三种: 等待阻塞、同步阻塞、其他阻塞:
死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
● 请问什么是死锁(deadlock)?
两个线程或两个以上线程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是这些线程都陷入了无限的等待中。如何避免线程死锁?
只要破坏产生死锁的四个条件中的其中一个就可以了。
破坏互斥条件:这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件:一次性申请所有的资源。
破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放
● JAVA中如何确保N个线程可以访问N个资源,但同时又不导致死锁?
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
常用线程池,线程池有哪几个参数
Java通过Executors提供四种线程池,分别为:
1)newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
参数
corePoolSize :核心线程数量
maximumPoolSize :线程最大线程数
workQueue :阻塞队列,存储等待执行的任务 很重要 会对线程池运行产生重大影响
keepAliveTime :线程没有任务时最多保持多久时间终止
unit :keepAliveTime的时间单位
threadFactory :线程工厂,用来创建线程
rejectHandler :当拒绝处理任务时的策略
线程池怎么用
ExecutorService cachePool = Executors.newCachedThreadPool(); cachePool.execute(getThread(i));
● Synchronized和lock
synchronized当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成F死锁现象,因此使用Lock时需要在finally块中释放锁;Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
● Syncronized锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么?
synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。
synchronized修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁。
synchronized 和 CAS 的区别
synchronized
采用的是 CPU 悲观锁机制,即线程获得的是独占锁, 其他线程只能依靠阻塞来等待线程释放锁。而在 CPU 转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起 CPU 频繁的上下文切换导致效率很低。尽管 Java1.6 为 synchronized
做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。
CAS 是比较并替换。它当中使用了3个基本操作数:内存地址 V,旧的预期值 A,要修改的新值 B。采用的是一种乐观锁的机制,它不会阻塞任何线程,所以在效率上,它会比 synchronized
要高。所谓乐观锁就是:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
所以,在并发量非常高的情况下,我们尽量的用同步锁,而在其他情况下,我们可以灵活的采用 CAS 机制。
synchronized关键字和volatile关键字比较:
- volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。
- 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞
- volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized关键字解决的是多个线程之间访问资源的同步性。
线程池运行原理分析
1、创建一个线程池,在还没有任务提交的时候,默认线程池里面是没有线程的。也可以调用prestartCoreThread方法,来预先创建一个核心线程。
2、线程池里还没有线程或者线程池里存活的线程数小于核心线程数corePoolSize时,这时对于一个新提交的任务,线程池会创建一个线程去处理提交的任务。此时线程池里面的线程会一直存活着,就算空闲时间超过了keepAliveTime,线程也不会被销毁,而是一直阻塞在那里一直等待任务队列的任务来执行。
3、当线程池里面存活的线程数已经等于corePoolSize了,对于一个新提交的任务,会被放进任务队列workQueue排队等待执行。而之前创建的线程并不会被销毁,而是不断的去拿阻塞队列里面的任务,当任务队列为空时,线程会阻塞,直到有任务被放进任务队列,线程拿到任务后继续执行,执行完了过后会继续去拿任务。这也是为什么线程池队列要是用阻塞队列。
4、当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列也满了,这里假设maximumPoolSize>corePoolSize(如果等于的话,就直接拒绝了),这时如果再来新的任务,线程池就会继续创建新的线程来处理新的任务,知道线程数达到maximumPoolSize,就不会再创建了。这些新创建的线程执行完了当前任务过后,在任务队列里面还有任务的时候也不会销毁,而是去任务队列拿任务出来执行。在当前线程数大于corePoolSize过后,线程执行完当前任务,会有一个判断当前线程是否需要销毁的逻辑:如果能从任务队列中拿到任务,那么继续执行,如果拿任务时阻塞(说明队列中没有任务),那超过keepAliveTime时间就直接返回null并且销毁当前线程,直到线程池里面的线程数等于corePoolSize之后才不会进行线程销毁。
5、如果当前的线程数达到了maximumPoolSize,并且任务队列也满了,这种情况下还有新的任务过来,那就直接采用拒绝的处理器进行处理。默认的处理器逻辑是抛出一个RejectedExecutionException异常。
● 请说明一下线程池有什么优势?
第一:降低资源消耗。第二:提高响应速度。第三:提高线程的可管理性
线程池的运行流程
首先判断核心线程池里的线程是否都在执行任务,如果不是则直接从核心线程池中创建一个线程来执行,如果都在忙则判断任务队列是否也满了,没满的话将任务放进去等待执行,满了就判断线程池的全部线程是否都在忙,如果都在忙就交给饱和策略来处理,否则就创建一个线程来帮助核心线程处理任务。
线程阻塞
- BIO,同步阻塞式IO,简单理解:一个连接一个线程
- NIO,同步非阻塞IO,简单理解:一个请求一个线程
- AIO,异步非阻塞IO,简单理解:一个有效请求一个线程
AQS原理
抽象的队列式同步器,是除了java自带的synchronized关键字之外的锁机制,AQS的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
CLH队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。
AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配
14、JVM
主内存:多个线程共享的内存,方法区和堆属于主内存区域。线程工作内存:每个线程独享的内存。虚拟机栈、本地方法栈、程序计数器属于线程独享的工作内存
Java创建对象的过程:
● JVM加载class文件的原理是什么?
JVM中类的装载是由ClassLoader和它的子类来实现的,Java ClassLoader 是一个Java运行时系统组件。它负责在运行时查找和装入类文件的类。
Java中的所有类都需要由类加载器装载到JVM中才能运行。类加载器的工作就是把class文件从硬盘读取到内存中。
双亲委派机制:
如果一个类加载器收到了类加载请求,他并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终达到顶层的启动类加载器
如果父类加载器可以完成类加载任务就成功返回,如果父类加载器不能完成加载任务,子加载器互尝试自己去加载
堆内存的分配策略:
1对象优先在eden区分配,Eden区没有足够的空间,将触发一次Minor GC。
2大对象直接进入老年代(为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率 ),
3长期存活的对象进入老年代
4动态对象年龄判断:如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入到老年代,无需等到MaxTenuringThreshold中要求的年龄
5空间分配担保:-XX: HandlePromotionFailure
Minor GC与Full GC分别在什么时候发生?
触发MinorGC(Young GC)
虚拟机在进行minorGC之前会判断老年代最大的可用连续空间是否大于新生代的所有对象总空间
1、如果大于的话,直接执行minorGC
2、如果小于,判断是否开启HandlerPromotionFailure,没有开启直接FullGC
3、如果开启了HanlerPromotionFailure, JVM会判断老年代的最大连续内存空间是否大于历次晋升的大小,如果小于直接执行FullGC
触发FullGC
老年代空间不足
如果创建一个大对象,Eden区域当中放不下这个大对象,会直接保存在老年代当中,如果老年代空间也不足,就会触发Full GC。为了避免这种情况,最好就是不要创建太大的对象。
持久代空间不足
如果有持久代空间的话,系统当中需要加载的类,调用的方法很多,同时持久代当中没有足够的空间,就出触发一次Full GC
YGC出现promotion failure
Minor GC 和 Full GC 有什么不同呢?
新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor
GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上
分代收集器:
老生代和新生代两个区域,而新生代又会分为:Eden 区和两个 Survivor区(From Survivor、To Survivor)
为什么 Survivor 分区不能是 0 个?
如果 Survivor 是 0 的话,也就是说新生代只有一个 Eden 分区,每次垃圾回收之后,存活的对象都会进入老生代,这样老生代的内存空间很快就被占满了,从而触发最耗时的 Full GC ,显然这样的收集器的效率是我们完全不能接受的。
为什么 Survivor 分区不能是 1 个?
如果 Survivor 分区是 1 个的话,假设我们把两个区域分为 1:1,那么任何时候都有一半的内存空间是闲置的,显然空间利用率太低不是最佳的方案。但如果设置内存空间的比例是 8:2 ,只是看起来似乎“很好”,假设新生代的内存为 100 MB( Survivor 大小为 20 MB ),现在有 70 MB 对象进行垃圾回收之后,剩余活跃的对象为 15 MB 进入 Survivor 区,这个时候新生代可用的内存空间只剩了 5 MB,这样很快又要进行垃圾回收操作,显然这种垃圾回收器最大的问题就在于,需要频繁进行垃圾回收。
为什么 Survivor 分区是 2 个?
如果 Survivor 分区有 2 个分区,我们就可以把 Eden、From Survivor、To Survivor 分区内存比例设置为 8:1:1 ,那么任何时候新生代内存的利用率都 90% ,这样空间利用率基本是符合预期的。再者就是虚拟机的大部分对象都符合“朝生夕死”的特性,所以每次新对象的产生都在空间占比比较大的 Eden 区,垃圾回收之后再把存活的对象方法存入 Survivor 区,如果是 Survivor 区存活的对象,那么“年龄”就 +1 ,当年龄增长到 15 (可通过设定)对象就升级到老生代。
● 请说明一下垃圾回收的优点以及原理
使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,Java中的对象不再有"作用域"的概念,只有对象的引用才有"作用域"。垃圾回收可以有效的防止内存泄露,有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。不再会被使用的对象的内存不能被回收,就是内存泄露
判断对象是否可回收的方法
引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 1;当引用失效,计数器就减 1;任何时候计数器为 0 的对象就是不可能再被使用的
可达性分析法:通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过
的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的
强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、软引用能带来的好处)
1.强引用
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必
不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java 虚拟机宁愿抛出 OutOfMemoryError 错
误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
2.软引用
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会回收它,如
果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可
用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中
3.弱引用
如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在:只具有弱引用的对象
拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就
会把这个弱引用加入到与之关联的引用队列中。
4.虚引用
虚引用主要用来跟踪对象被垃圾回收的活动 ,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,因为软引用可以加速 JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出等问题的产生**
如何判断一个常量是废弃常量
运行时常量池主要回收的是废弃的常量。
假如在常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是
废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。
如何判断一个类是无用的类
方法区主要回收的是无用的类,类需要同时满足下面 3 个条件才能算是 “无用的类” :
1、该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
2、加载该类的 ClassLoader 已经被回收。
3、该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
垃圾回收算法:
1、标记-清除算法
算法分为“标记”和“清除”阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。它是
最基础的收集算法,效率也很高,但是会带来两个明显的问题:\1. 效率问题\2. 空间问题
2、复制算法
为了解决效率问题,“复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块
的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收
都是对内存区间的一半进行回收。
3、标记-整理算法
根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
4、分代收集算法
当前虚拟机的垃圾收集都采用分代收集算法,根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以
完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须
选择“标记-清除”或“标记-整理”算法进行垃圾收集。
垃圾收集器
1、 Serial 收集器 它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程,直到它收集结束。 新生代采用复制算法,老年代采用标记-整理算法
2、ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样
3 、Parallel Scavenge 收集器它关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。 新生代采用复制算法,老年代采用标记-整理算法。
4、Serial Old 收集器,它是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的
版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
5 、Parallel Old 收集器
6 、CMS 收集器,是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作
7、G1 收集器是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满
足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
volatile的作用
保证共享变量的可见性:使用volatile修饰的变量,任何线程对其进行操作都是在主内存中进行的,不会产生副本,从而保证共享变量的可见性。防止局部指令重排序:happens-before规则中的volatile变量规则规定了一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。volatile如何防止指令重排序
volatile是通过内存屏障来防止指令重排序的。volatile防止指令重排序具体步骤:
在每个volatile写操作的前面插入一个StoreStore屏障。在每个volatile写操作的后面插入一个StoreLoad屏障。在每个volatile读操作的后面插入一个LoadLoad屏障。在每个volatile读操作的后面插入一个LoadStore屏障。
15、项目
认证授权是如何实现的? Spring security + Oauth2完成用户认证及用户授权。认证授权流程如下:
1、用户请求认证服务完成身份认证。
2、认证服务下发用户身份令牌和JWT令牌,拥有身份令牌表示身份合法,Jwt令牌用于完成授权。
3、用户携带jwt令牌请求资源服务。
4、网关校验用户身份令牌的合法,不合法表示用户没有登录,如果合法则放行继续访问。
5、资源服务获取jwt令牌,根据jwt令牌完成授权
认证与授权实现思路
如果系统的模块多,每个模块都需要就行授权与认证,所以选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问
使用rabbitMQ
1、平台包括多个站点,页面归属不同的站点,需求是发布一个页面应将该页面发布到所属站点的服务器上。
2、每个站点服务部署CMS Client程序,并与交换机绑定,绑定时指定站点Id为routingKey。
指定站点id为routingKey就可以实现cms client只能接收到所属站点的页面发布消息。
3、页面发布程序向MQ发布消息时指定页面所属站点Id为routingKey,根据routingKey将消息发给指定的
CMS Client。
分布式事务:
1、在微服务中使用Spring 声明式事务控制方式进行控制,在Service方法上添加@Transctional注解即可实现事务
控制,它控制的是MySQL的本地事务。
2、项目中存在分布式事务控制,比如下单支付、课程发布等地址都用到了分布式事务。
本项目实现分布式事务控制实现最终数据一致性,做法是:
a、将分布式事务拆分为多个本地事务。
b、提交事务前每个参与者要通过数据校验,和资源预留。
c、由消息队列去通知多个事务参与者完成本地事务的提交。
d、提交失败的本地事务会重试。
项目中课程搜索采用ElasticSearch来完成。实现方法是:
1、使用 Logstash(logstash是ES下的一款开源软件,它能够同时 从多个来源采集数据、转换数据)将MySQL中
的课程信息读取到ES中创建索引,使用IK分词器进行分词。
2、使用 Java High Level REST Client完成搜索。
3、生产环境使用ES部署为集群。
系统对异常的处理使用统一的异常处理流程。
1、自定义异常类型。
2、自定义错误代码及错误信息。
3、对于可预知的异常由程序员在代码中主动抛出自定义异常类型的异常,抛出异常时需要指定错误代码。
4、对于不可预知的异常(运行时异常)由SpringMVC统一捕获Exception类型的异常,由统一的异常捕获类来解析处理,并转换为与自定义异常类型一致的信息格式(错误代码+错误信息)。
5、可预知的异常及不可预知的运行时异常最终会采用统一的信息格式(错误代码+错误信息)来表示,最终也会随请求响应给客户端。
http协议处理视频流
http采用DASH传输视频流,原理是DASH服务器将视频进行了切片,MPD是一个XML,为接收端播放器提供表示不同分片的URL、时序、比特率、清晰度等信息。客户端通过接受、解析MPD文件作为自适应流的依据,客户端基于MPD信息为分片发送HTTP请求,然后客户端的媒体播放器为收到的分片进行解码和播放。
你在开发中遇到什么问题?是怎么解决的?
例子:
在处理订单时要用到定时任务,当时采用的是Spring Task来完成,由于一个订单服务会部署多个,多个订单服务
同时去处理任务会造成任务被重复处理的情况,如何解决任务的重复处理。
解决:
采用乐观锁解决,在任务表中设置一个version字段记录版本号,取出任务记录同时拿到任务的版本号,执行前对
任务进行锁定,具体的做法是执行update根据当前版本号将版本号加1,update成功表示锁定任务成功,即可开始执行任务。
例子:
Redis服务器 can not get resource from pool. 1000个线程并发还能跑,5000个线程的时候出现这种问题,查后台debug日志,发现redis 线程池不够。刚开始设置的是:
解决:顺便也改了一下jdbc 的连接池参数,最大空闲和最大连接数都改成1000.在测一下。可以
例子:
注册中心和服务提供者集群注册失败,启动报错
解决:修改yml文件,修改启动类上的注解,yml文件缩进出了问题,服务端启动类上应该添加注解@EnableEurekaServer,客户端@EnableDiscoveryClient
例子:
xml文件配置错误,页面访问一直无数据
解决:根据浏览器的开发工具,定位错误,检查xml文件的SQL,namespace,parameterType等是否正确,是否假如应有的注解
例子:
git合并冲突,甲乙都是根据point.java文件进行开发,甲开发出来版本2,并且提交了代码,乙开发出来版本3,也需要提交代码,此时将会报错存在冲突。因为甲提交版本之后,此时远端的代码已经是版本2了,而乙是在版本1的基础上进行开发出了版本3,所以乙想要提交代码,势必要将自己的代码更新为版本2的代码,然后在进行提交
解决:拉去远端代码,存在冲突,会报错。此时我们需要将本地代码暂存起来stash;更新本地代码,将本地代码版本更新和远端的代码一致,将暂存的代码合并到更新后的代码后,有冲突的要手动解决冲突,然后提交解决冲突后的代码。Git<resolve conflict
例子:
支付接口采用微信的扫码支付拿到需求后,首先去阅读微信的接口文档,重点阅读统一下单、支付结果通知、支付结果查询三个接口。下载官方提供的sdk编写单元测试用例测试每个接口。测试时没有使用微信的沙箱测试,直接使用正式接口,将金额改的小一些进行测试。单元测试通过后开发整个支付功能,最终集成测试通过。
解决:订单支付接口参数的签名问题,当时是因为自己没有仔细看接口文档导致少写一个必填参数一直报签名失败,随后将所有必填参数填写完成,最终解决问题。
例子:
一个接口出现Bug你是怎么调试的?
1、接口的开发需要前端和服务端共同调试,要仔细阅读测试人员反映的bug信息,判断这个bug是服务端的bug还
是前端的bug。通常服务接口开发完成会使用postman工具进行测试,测试没有问题再提交到Git或SVN。
2、找到bug的出错点就可以根据bug信息进行修改。修改完成需要前后端再次连调测试,按照测试人员提交的测试流程重新进行测试,测试通过将此bug置为已解决。