在IoC容器中,bean的获取主要经过BeanFactory和ApplicationContext获取,这里ApplicationContext其实是继承自BeanFactory的,二者的区别在于BeanFactory对bean的初始化主要是延迟初始化的方式,而ApplicationContext对bean的初始化是在容器启动时即将全部bean初始化完毕。javascript
Spring IOC容器建立一个Bean实例时,能够为Bean指定实例的做用域,做用域包括singleton(单例模式)、prototype(原型模式)、request(HTTP请求)、session(会话)、global-session(全局会话)css
单例(singleton):它是默认的选项,在整个应用中,Spring只为其生成一个Bean的实例。html
原型(prototype):当每次注入,或者经过Spring IoC容器获取Bean时,Spring都会为它建立一个新的实例。前端
会话(session):在Web应用中使用,就是在会话过程当中Spring只建立一个实例。java
请求(request):在Web应用中使用的,就是在一次请求中Spring会建立一个实例,可是不一样的请求会建立不一样的实例。mysql
全局会话(global-session):全局会话内有效,假如你在编写一个标准的基于Servlet的web应用,而且定义了一个或多个具备global session做用域的bean,系统会使用标准的HTTP Session做用域,而且不会引发任何错误。web
事务特性(4种): 面试
原子性 (atomicity):强调事务的不可分割. redis
一致性 (consistency):事务的执行的先后数据的完整性保持一致. 算法
隔离性 (isolation):一个事务执行的过程当中,不该该受到其余事务的干扰
持久性(durability) :事务一旦结束,数据就持久到数据库
脏读 :脏读就是指当一个事务正在访问数据,而且对数据进行了修改,而这种修改尚未提交到数据库中,这时,另一个事务也访问这个数据,而后使用了这个数据。
不可重复读 :是指在一个事务内,屡次读同一数据。在这个事务尚未结束时,另一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,因为第二个事务的修改,那么第一个事务两次读到的的数据多是不同的。这样就发生了在一个事务内两次读到的数据是不同的,所以称为是不 可重复读。
虚幻读 :是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的所有数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,之后就会发生操做第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉同样。.
DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生
已提交读 (read commited):避免脏读。可是不可重复读和虚读有可能发生
可重复读 (repeatable read) :避免脏读和不可重复读.可是虚读有可能发生.
串行化的 (serializable) :避免以上全部读问题.
Mysql 默认:可重复读
Oracle 默认:读已提交
read uncommited:是最低的事务隔离级别,它容许另一个事务能够看到这个事务未提交的数据。
read commited:保证一个事物提交后才能被另一个事务读取。另一个事务不能读取该事物未提交的数据。
repeatable read:这种事务隔离级别能够防止脏读,不可重复读。可是可能会出现幻象读。它除了保证一个事务不能被另一个事务读取未提交的数据以外还避免了如下状况产生(不可重复读)。
serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读以外,还避免了幻象读(避免三种)。
* 保证同一个事务中
Propagation_required:PROPAGATION_REQUIRED 支持当前事务,若是不存在 就新建一个(默认)
Propagation_supports:PROPAGATION_SUPPORTS 支持当前事务,若是不存在,就不使用事务
Propagation_mandatory:PROPAGATION_MANDATORY 支持当前事务,若是不存在,抛出异常
* 保证没有在同一个事务中
Propagation_requires_new:PROPAGATION_REQUIRES_NEW 若是有事务存在,挂起当前事务,建立一个新的事务
Propagation_not_supported:PROPAGATION_NOT_SUPPORTED 以非事务方式运行,若是有事务存在,挂起当前事务
Propagation_never:PROPAGATION_NEVER 以非事务方式运行,若是有事务存在,抛出异常
Propagation_nested:PROPAGATION_NESTED 若是当前事务存在,则嵌套事务执行
@Transactional 注解只能应用到 public 方法才有效。
若是不生效,加上@Transactional(rollbackFor = Exception.class)
缘由是:当咱们使用@Transaction 时默认为RuntimeException(也就是运行时异常)异常才会回滚。
@SpringBootApplication:申明让spring boot自动给程序进行必要的配置,这个配置等同于:
@Configuration ,@EnableAutoConfiguration 和 @ComponentScan 三个配置。
@ComponentScan:让spring Boot扫描到Configuration类并把它加入到程序上下文。
@Configuration :等同于spring的XML配置文件;使用Java代码能够检查类型安全。
@EnableAutoConfiguration :自动配置。
1)@SpringBootApplication注解主配置类里边最主要的功能就是SpringBoot开启了一个@EnableAutoConfiguration注解的自动配置功能。
2)@EnableAutoConfiguration(开启自动配置)做用:它主要利用了一个EnableAutoConfigurationImportSelector选择器给Spring容器中来导入一些组件。
3)开启自动配置导入选择器调用、selectImports()方法经过SpringFactoriesLoader.loadFactoryNames()扫描全部具备META-INF/spring.factories的jar包。
4)这个spring.factories文件也是一组一组的key=value的形式,其中一个key是X类的全类名,而它的value是一个X的类名的列表,这些类名以逗号分隔.pring.factories文件,则是用来记录项目包外须要注册的bean类名。
5)这个@EnableAutoConfiguration注解经过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(...)的内部就会执行selectImports()方法,找到全部JavaConfig自动配置类的全限定名对应的class,而后将全部自动配置类加载到Spring容器中。
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。采起的是空间换时间的概念。
MyISAM引擎和InnoDB引擎使用B+Tree做为索引结构
1)普通索引,这是最基本的索引,它没有任何限制,好比上文中为title字段建立的索引就是一个普通索引,MyIASM中默认的BTREE类型的索引,也是咱们大多数状况下用到的索引。
2)惟一索引,与普通索引相似,不一样的就是:索引列的值必须惟一,但容许有空值(注意和主键不一样)。若是是组合索引,则列值的组合必须惟一,建立方法和普通索引相似。
3)全文索引(FULLTEXT),对于较大的数据集,将你的资料输入一个没有FULLTEXT索引的表中,而后建立索引,其速度比把资料输入现有FULLTEXT索引的速度更为快。不过切记对于大容量的数据表,生成全文索引是一个很是消耗时间很是消耗硬盘空间的作法。
4)单列索引、多列索引,多个单列索引与单个多列索引的查询效果不一样,由于执行查询时,MySQL只能使用一个索引,会从多个索引中选择一个限制最为严格的索引。
5)组合索引(最左前缀),平时用的SQL查询语句通常都有比较多的限制条件,因此为了进一步榨取MySQL的效率,就要考虑创建组合索引
好比字段a,b,c创建复合索引。
where a=1 and c=4 and b=10 能够利用到索引 (a,b,c),即便顺序乱也能够
where a=1能够利用到索引 (a,b,c)
where b=5没法利用索引 (a,b,c)
1)MyISAM 不支持事务,不支持外键,优点是访问速度快,对事务完整性没有要求,或者以select、insert为主的能够使用
2)InnoDB 支持事务,外键约束,自增,写的效率差一些,更占据空间,支持行级锁
3)Memory 使用内存中的内容来建立表,访问速度很是快,使用哈希索引。可是一旦服务关闭,表中的数据就会丢失。
4)Merge 是一组MyISAM表的组合,这些表必须结构彻底相同,merge自己没有数据。对merge的查询、更新、删除实际是对MyISAM的修改。
1)InnoDB支持事务,MyISAM不支持。
2)MyISAM适合查询以及插入为主的应用,InnoDB适合频繁修改以及涉及到安全性较高的应用。
3)InnoDB支持外键,MyISAM不支持。
4)从MySQL5.5.5之后,InnoDB是默认引擎。
5)MyISAM支持全文类型索引,而InnoDB不支持全文索引。
6)InnoDB中不保存表的总行数,select count(*) from table时,InnoDB须要扫描整个表计算有多少行,但MyISAM只需简单读出保存好的总行数便可。注:当count(*)语句包含where条件时MyISAM也需扫描整个表。
7)对于自增加的字段,InnoDB中必须包含只有该字段的索引,可是在MyISAM表中能够和其余字段一块儿创建联合索引。
8)清空整个表时,InnoDB是一行一行的删除,效率很是慢。MyISAM则会重建表。MyisAM使用delete语句删除后并不会马上清理磁盘空间,须要定时清理,命令:OPTIMIZE table dept;
9)InnoDB支持行锁(某些状况下仍是锁整表,如 update table set a=1 where user like ‘%lee%’)
10)Myisam建立表生成三个文件:.frm 数据表结构 、 .myd 数据文件 、 .myi 索引文件,Innodb只生成一个 .frm文件,数据存放在ibdata1.log
如今通常都选用InnoDB,主要是MyISAM的全表锁,读写串行问题,并发效率锁表,效率低,MyISAM对于读写密集型应用通常是不会去选用的。
应用场景:
MyISAM不支持事务处理等高级功能,但它提供高速存储和检索,以及全文搜索能力。若是应用中须要执行大量的SELECT查询,那么MyISAM是更好的选择。
InnoDB用于须要事务处理的应用程序,包括ACID事务支持。若是应用中须要执行大量的INSERT或UPDATE操做,则应该使用InnoDB,这样能够提升多用户并发操做的性能。
1)避免所有扫描,好比对null值进行筛选判读;使用!=或<>、like、or等等都将放弃索引全表扫描
2)考虑在where及order by涉及的列上创建索引
3)使用正向逻辑(not in,not exists)
4)数据库不擅长运算,把运算交给逻辑代码,非要有把运算放在右边
5)合理建表,使用合理的字段,善用非空、外键约束保证数据的完整性
6)索引并非越多越好,一个表最好不要超过6个,多了影响增、删、改的性能。这个影响很大
7)多从业务逻辑方面考虑问题,合理使用中间件
8)对于数据量太大的数据分库分表,使用中间件好比mycat
252、分表分库
①:垂直分割(并不经常使用)
就是将一个表按照字段来分,每张表保证有相同的主键就好。通常来讲,将经常使用字段和大字段分表来放。
优点:比没有分表来讲,提升了查询速度,下降了查询结果所用内存;
劣势:没有解决大量记录的问题,对于单表来讲随着记录增多,性能仍是降低很快;
②: 水平分割(重要,实际应用中使用最多)
水平分割是企业最经常使用到的,水平拆分就是大表按照记录分为不少子表:
水平分的规则彻底是自定义的,有如下几种参考设计:
1 hash、自增id取模:
对某个字段进行hash来肯定建立几张表,并根据hash结果存入不一样的表;
2 按时间
根据业务能够按照天、月、年来进行拆分;
3 按每一个表的固定记录数
通常按照自增ID进行拆表,一张表的数据行到了指定的数量,就自动保存到下一张表中。好比规定一张表只能存1-1000个记录;
4 将老数据迁移到一张历史表
好比日志表,通常只查询3个月以内的数据,对于超过3个月的记录将之迁移到历史子表中;
(1)FROM [left_table]
(2)ON <join_condition>
(3)<join_type> JOIN <right_table>
(4)WHERE <where_condition>
(5)GROUP BY <group_by_list>
(6)WITH <CUBE | RollUP>
(7)HAVING <having_condition>
(8)SELECT
(9)DISTINCT
(10)ORDER BY <order_by_list>
(11)<Top Num> <select list>
GROUP BY表示分组,按某一个字段进行分组
HAVING是对于GROUP BY对象进行筛选
外层查询表小于子查询表,则用exists,外层查询表大于子查询表,则用in,若是外层和子查询表差很少,则爱用哪一个用哪一个
1)St ing字符串:格式: set key value
string类型是二进制安全的。意思是redis的string能够包含任何数据。好比jpg图片或者序列化的对象 。string类型是Redis最基本的数据类型,一个键最大能存储512MB。
2)Hash(哈希)格式: hmset name key1 value1 key2 value2
Redis hash 是一个键值(key=>value)对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
3) lsit(列表)Redis 列表是简单的字符串列表,按照插入顺序排序。你能够添加一个元素到列表的头部(左边)或者尾部(右边)
4)set(集合)
5)zset(有序集合)
Redis zset 和 set 同样也是string类型元素的集合,且不容许重复的成员。
不一样的是每一个元素都会关联一个double类型的分数。redis正是经过分数来为集合中的成员进行从小到大的排序。
zset的成员是惟一的,但分数(score)却能够重复。
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis 提供了两种持久化方式:RDB(默认) 和AOF
RDB:rdb是Redis DataBase缩写
功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数
AOF:Aof是Append-only file缩写
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行如下两个工做
aof写入保存:
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
比较:
1、aof文件比rdb更新频率高,优先使用aof还原数据。
2、aof比rdb更安全也更大
3、rdb性能比aof好
4、若是两个都配了优先加载AOF
1)首先redis是默认永不过时的,若是要手动设置时间,须要增量设置过时时间,避免redis中的缓存在同一时间失效。若是是系统级别的缘由,好比宕机,采用主从复制。
2)缓存穿透的问题,经过设置分布式锁,取得锁的进程操做数据库并更新缓存,没取得锁的进程发现有锁就等待。
1)加锁操做:jedis.set(key,value,"NX","EX",timeOut)。
key就是redis的key值做为锁的标识,value在这里做为客户端的标识,只有key-value都比配才有删除锁的权利【保证安全性】
NX:只有这个key不存才的时候才会进行操做,if not exists;
EX:设置key的过时时间为秒,具体时间由第5个参数决定
经过timeOut设置过时时间保证不会出现死锁【避免死锁】
2)解锁操做:unLock(String key,String value)
执行一个lua脚本,若是根据key拿到的value跟传入的value相同就执行del,不然就返回
3)重试机制:lockRetry(String key,String value,Long timeOut,Integer retry,Long sleepTime)
主要是用于其余进程,若是没有发现有锁就进入睡眠状态,设置睡眠时间,以及重试次数(循环次数)
jS作一个状态码false,当提交成功后状态码为true,提交前先验证这个状态码是否为false,不然就返回
WXML,WXSS,javascript,json
消息中间件是程序相互通讯的一种方式,消息队列是消息中间件的一种实现方式。
1)消息没有收到,使用事务(有这个注解),接收到消息就返回一个状态,不然重复发送。性能会下降,相似于微信支付宝支付的异步通知。
2)MQ保存消息丢失,这种状况基本不存在。AMQ是一种文件存储形式,它具备写入速度快和容易恢复的特色。消息存储在一个个文件中,文件的默认大小为32M,若是一条消息的大小超过了32M,那么这个值必须设置大一点。当一个存储文件中的消息已经所有被消费,那么这个文件将被标识为可删除,在下一个清除阶段,这个文件被删除。
若是要消息记录能够考虑持久化到数据库中
dubbo支持不一样的通讯协议
1)dubbo协议
dubbo://192.168.0.1:20188
默认就是走dubbo协议的,单一长链接,NIO异步通讯,基于hessian做为序列化协议(默认)
适用的场景就是:传输数据量很小(每次请求在100kb之内),可是并发量很高
为了要支持高并发场景,通常是服务提供者就几台机器,可是服务消费者有上百台,可能天天调用量达到上亿次!此时用长链接是最合适的,就是跟每一个服务消费者维持一个长链接就能够,可能总共就100个链接。而后后面直接基于长链接NIO异步通讯,能够支撑高并发请求。
不然若是上亿次请求每次都是短链接的话,服务提供者会扛不住。
并且由于走的是单一长链接,因此传输数据量太大的话,会致使并发能力下降。因此通常建议是传输数据量很小,支撑高并发访问。
2)rmi协议
走java二进制序列化,多个短链接,适合消费者和提供者数量差很少,适用于文件的传输,通常较少用
3)hessian协议
走hessian序列化协议,多个短链接,适用于提供者数量比消费者数量还多,适用于文件的传输,通常较少用
4)http协议
走json序列化
5)webservice
走SOAP文本序列化
1)服务提供者在启动时,向服务注册中心注册本身提供的服务
2)服务消费者在启动时,向注册中心订阅本身所须要的服务
3)注册中心返回服务提供者地址列表给服务消费者。若是有变动,服务注册中心将使用长链接推送变动数据给消费者
4)服务消费者,从服务提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用。若是调用失败,再选另外一台调用
5) 服务消费者和服务提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次数据到监控中心
zookeeper提供了三种方式:
LeaderElection,AuthFastLeaderElection(受权快速领导人选举),FastLeaderElection
默认的算法是FastLeaderElection(快速选择领导),因此主要分析它的选举机制。
当启动初始化集群的时候,server1的myid为1,zxid为0 server2的myid为2,zxid一样是0,以此类推。此种状况下zxid都是为0。先比较zxid,再比较myid服务器1启动,给本身投票,而后发投票信息,因为其它机器尚未启动因此它收不到反馈信息,服务器1的状态一直属于Looking(选举状态)。
服务器2启动,给本身投票,同时与以前启动的服务器1交换结果,因为服务器2的myid大因此服务器2胜出,但此时投票数没有大于半数,因此两个服务器的状态依然是LOOKING。
服务器3启动,给本身投票,同时与以前启动的服务器1,2交换信息,因为服务器3的myid最大因此服务器3胜出,此时投票数正好大于半数,因此服务器3成为领导者,服务器1,2成为小弟。
服务器4启动,给本身投票,同时与以前启动的服务器1,2,3交换信息,尽管服务器4的myid大,但以前服务器3已经胜出,因此服务器4只能成为小弟。
服务器5启动,后面的逻辑同服务器4成为小弟
当选举机器过半的时候,已经选举出leader后,后面的就跟随已经选出的leader,因此4和5跟随成为leader的server3
因此,在初始化的时候,通常到过半的机器数的时候谁的myid最大通常就是leader
运行期间
按照上述初始化的状况,server3成为了leader,在运行期间处于leader的server3挂了,那么非Observer服务器server1、server2、server4、server5会将本身的节点状态变为LOOKING状态
1、开始进行leader选举。如今选举一样是根据myid和zxid来进行
2、首先每一个server都会给本身投一票竞选leader。假设server1的zxid为123,server2的zxid为124,server4的zxid为169,server5的zxid为188
3、一样先是比较zxid再比较,server1、server2、server4比较server4根据优先条件选举为leader。而后server5仍是跟随server4,即便server5的zxid最大,可是当选举到server4的时候,机器数已通过半。再也不进行选举,跟随已经选举的leader
zookeeper集群为保证数据的一致性全部的操做都是由leader完成,以后再由leader同步给follower。重点就在这儿,zookeeper并不会确保全部节点都同步完数据,只要有大多数节点(即n/2+1)同步成功便可。
我们假设有一个写操做成功那么如今数据只存在于节点leader,以后leader再同步给其余follower。这时候宕掉3个机器,已通过半的机器没法进行投票选举,剩余2台不足过半,没法选举=没法提供任何服务。再启动一个机器恢复服务。因此宕掉的机器不要过半,过半就会致使没法正常服务
1)制器核心类:
org.springframework.web.servlet.DispatcherServlet - 配置web.xml
2)加载配置文件核心类:
org.springframework.web.context.ContextLoaderListener – spring的配置文件
3)处理url影射核心类:
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping-根据bean的名称请求一个bean. spring的配置文件- /abc
4)处理视图资源核心类:
org.springframework.web.servlet.view.ResourceBundleViewResolver
5)方法动态调用核心类
org.springframework.web.servlet.mvc.multiaction.ParameterMethodNameResolver
双向链表也叫双链表,是链表的一种,它的每一个数据结点中都有两个指针,分别指向直接后继和直接前驱。因此,从双向链表中的任意一个结点开始,均可以很方便地访问它的前驱结点和后继结点。
1)Lock是一个接口,而synchronized是关键字。
2)synchronized会自动释放锁,而Lock必须手动释放锁。
3)Lock可让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
4)经过Lock能够知道线程有没有拿到锁,而synchronized不能。
5)Lock能提升多个线程读操做的效率。
6)synchronized能锁住类、方法和代码块,而Lock是块范围内的
为了能让 HashMap 存取高效,尽可能较少碰撞,也就是要尽可能把数据分配均匀,每一个链表/红黑树长度大体相同。这个实现就是把数据存到哪一个链表/红黑树中的算法。
建立 AuthenticationToken,而后调用 Subject.login 方法进行登陆认证;
Subject 委托给 SecurityManager;
SecurityManager 委托给 Authenticator 接口;
Authenticator 接口调用 Realm 获取登陆信息。
1、Subject :当前用户的操做
2、SecurityManager:用于管理全部的Subject
3、Realms:用于进行权限信息的验证,认证受权都在这里
在shiro的用户权限认证过程当中其经过两个方法来实现:
1、Authentication:是验证用户身份的过程。重写doGetAuthenticationInfo()方法
2、Authorization:是受权访问控制,用于对用户进行的操做进行人证受权,证实该用户是否容许进行当前操做,如访问某个连接,某个资源文件等,重写doGetAuthorizationInfo()
3、实现权限is or须要重写AuthorizationFilter中的isAccessAllowed(),循环遍历角色的权限数组,只要包含其一就返回true
其余组件:
除了以上几个组件外,Shiro还有几个其余组件:
1、SessionManager :Shiro为任何应用提供了一个会话编程范式。
2、CacheManager :对Shiro的其余组件提供缓存支持。
4、remenberMe:记住我
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,能够根据对象关系模型直接获取,因此它是全自动的。而Mybatis在查询关联对象或关联集合对象时,须要手动编写sql来完成,因此,称之为半自动ORM映射工具。
association 一对一, 一对多 collection,多对多 discrimination
有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 经过在resultMap里面配置association节点配置一对一的类就能够完成;
嵌套查询是先查一个表,根据这个表里面的结果的 外键id,去再另一个表里面查询数据,也是经过association配置,但另一个表的查询经过select属性配置。
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,经过在resultMap里面的collection节点配置一对多的类就能够完成;嵌套查询是先查一个表,根据这个表里面的 结果的外键id,去再另一个表里面查询数据,也是经过配置collection,但另一个表的查询经过select节点配置。
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,能够配置是否启用延迟加载lazyLoadingEnabled=true|false。
它的原理是,使用CGLIB建立目标对象的代理对象,当调用目标方法时,进入拦截器方法,好比调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,而后调用a.setB(b),因而a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
固然了,不光是Mybatis,几乎全部的包括Hibernate,支持延迟加载的原理都是同样的。
1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储做用域为 Session,当 Session flush 或 close 以后,该 Session 中的全部 Cache 就将清空,默认打开一级缓存。
2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不一样在于其存储做用域为 Mapper(Namespace),而且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类须要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置<cache/> ;
3)对于缓存数据更新机制,当某一个做用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操做后,默认该做用域下全部 select 中的缓存将被 clear 掉并从新更新,若是开启了二级缓存,则只根据配置判断是否刷新。
一级缓存
session级别的缓存,当咱们使用了get load find Query等查询出来的数据,默认在session中就会有一份缓存数据,缓存数据就是从数据库将一些数据拷贝一份放到对应的地方.
一级缓存不可卸载: (只要使用了session,确定用到了session的缓存机制,是hibernate控制的,咱们不能手动配置)
一级缓存的清理:
close clear这两种方式会所有清理; evict方法是将指定的缓存清理掉
二级缓存
sessionFactory级别的缓存,能够作到多个session共享此信息
sessionFactory缓存分类:
1. 内缓存: 预制的sql语句,对象和数据库的映射信息
2. 外缓存:存储的是咱们容许使用二级缓存的对象
适合放在二级缓存中的数据:
1. 常常被修改的数据
2. 不是很想重要的数据,容许出现偶尔并发的数据
3. 不会被并发访问的数据
4. 参考数据
适合放到一级缓存中的数据:
1. 常常被修改的数据
2. 财务数据,绝对不容许出现并发
3. 与其它应用共享的数据
Hibernate的二级缓存策略的通常过程:
1. 条件查询的时候,
String hql = “from 类名”;
这样的SQL语句查询数据库,一次得到全部的数据库.
2.把得到的全部数据对象根据ID放入到第二级缓存中
3.当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,若是配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存
4.删除 更新 增长数据的时候,同时更新缓存
注: Hibernate的二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无做用.为此,Hibernate提供了针对条件查询的Query缓存
1.建立SpringApplication实例
1) 在SpringApplicaiton构造器中调用initialize(sources)方法。initialize方法中,将sources转换成list加到this.sources属性中。
2) 判断是否为web环境,在类路径下是否能够加载到Servlet和ConfigurableWebApplicationContext
3) 设置初始化器,从META-INF/spring.factories处读取配置文件中Key为:org.springframework.context.ApplicationContextInitializer的value,进行实例化操做
4) 设置监听器,StopWatch主要是监控启动过程,统计启动时间,检测应用是否已经启动或者中止。
5) 推断应用入口类,经过寻找main方法找到启动主类。
2.执行SpringApplication.run()
1) 获取SpringApplicationRunListeners,(也是经过META-INF/spring.factories),默认加载的是EventPublishingRunListener。启动监听,调用RunListener.starting()方法。
2) 根据SpringApplicationRunListeners以及参数来准备环境,获取环境变量environment,将应用参数放入到环境变量持有对象中,监听器监听环境变量对象的变化(listener.environmentPrepared),打印Banner信息(SpringBootBanner)
3) 建立ApplicationContext(spring上下文AnnotationConfigEmbeddedWebApplicationContext)
4) 建立FailureAnalyzer, 用于触发从spring.factories加载的FailureAnalyzer和FailureAnalysisReporter实例
5) spring上下文前置处理prepareContext
6) spring上下文刷新refreshContext
7) spring上下文后置处理afterRefresh(ApplicationRunner,CommandLineRunner接口实现类的启动),返回上下文对象
类加载的过程主要分为三个部分:加载;连接;初始化
而连接又能够细分为三个小部分:验证;准备;解析
1)加载
简单来讲,加载指的是把class字节码文件从各个来源经过类加载器装载入内存中。
这里有两个重点:
字节码来源:通常的加载来源包括从本地路径下编译生成的.class文件,从jar包中的.class文件,从远程网络,以及动态代理实时编译
类加载器:通常包括启动类加载器,扩展类加载器,应用类加载器,以及用户的自定义类加载器。
2)连接
验证:主要是为了保证加载进来的字节流符合虚拟机规范,不会形成安全错误。
包括对于文件格式的验证,好比常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其余信息?对于元数据的验证,好比该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?
对于字节码的验证,保证程序语义的合理性,好比要保证类型转换的合理性。
对于符号引用的验证,好比校验符号引用中经过全限定名是否可以找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
准备:主要是为类变量(注意,不是实例变量)分配内存,而且赋予初值。
特别须要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不一样变量类型的默认初始值。好比8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值,final static tmp = 456, 那么该阶段tmp的初值就是456
解析:将常量池内的符号引用替换为直接引用的过程。
两个重点:
符号引用。即一个字符串,可是这个字符串给出了一些可以惟一性识别一个方法,一个变量,一个类的相关信息。
直接引用。能够理解为一个内存地址,或者一个偏移量。好比类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量
举个例子来讲,如今调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。
在解析阶段,虚拟机会把全部的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
3)初始化
这个阶段主要是对类变量初始化,是执行类构造器的过程。
换句话说,只对static修饰的变量或语句进行初始化。
若是初始化一个类的时候,其父类还没有初始化,则优先初始化其父类。
若是同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
一方面是因为java代码很容易被反编译,若是须要对本身的代码加密的话,能够对编译后的代码进行加密,而后再经过实现本身的自定义类加载器进行解密,最后再加载。
另外一方面也有可能从非标准的来源加载代码,好比从网络来源,那就须要本身实现一个类加载器,从指定源进行加载。
Java类加载器是Java运行时环境的一部分,负责动态加载Java类到Java虚拟机的内存空间中。类一般是按需加载,即第一次使用该类时才加载。因为有了类加载器,Java运行时系统不须要知道文件与文件系统。学习类加载器时,掌握Java的委派概念很重要。
类加载器它是在虚拟机中完成的,负责动态加载Java类到Java虚拟机的内存空间中,在通过 Java 编译器编译以后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。
首先,前端js拦截,提交订单前先判断提交状态。只有未提交成功能够提交,提交成功后则禁止提交。后台,一个订单有惟一的编号,并且有新建、提交支付中,支付失败,未支付成功等状态。根据订单获取支付状态便可,失败能够继续提交支付,成功的返回结果便可。
1)2PC即两阶段提交协议,
是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit
phase),2是指两个阶段,P是指准备阶段,C是指提交阶段。须要数据库支持X/A协议
1. 准备阶段(Prepare phase):事务管理器给每一个参与者发送Prepare消息,每一个数据库参与者在本地执行事务,并写本地的Undo/Redo日志,此时事务没有提交。(Undo日志是记录修改前的数据,用于数据库回滚,Redo日志是记录修改后的数据,用于提交事务后写入数据文件)
2. 提交阶段(commit phase):若是事务管理器收到了参与者的执行失败或者超时消息时,直接给每一个参与者发送回滚(Rollback)消息;不然,发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或者回滚操做,并释放事务处理过程当中使用的锁资源。注意:必须在最后阶段释放锁资源。
1.1 XA方案(2PC协议)
2PC的传统方案是在数据库层面实现的,如Oracle、MySQL都支持2PC协议,为了统一标准减小行业内没必要要的对接成本,须要制定标准化的处理模型及接口标准,国际开放标准组织Open Group定义了分布式事务处理模型DTP(Distributed Transaction Processing Reference Model)。主要实现思想是一个应用程序拥有2个数据源,把两个数据库的操做合并到一个事务。
1.2 Seata方案
Seata是由阿里中间件团队发起的开源项目 Fescar,后改名为Seata,它是一个是开源的分布式事务框架。传统2PC的问题在Seata中获得了解决,它经过对本地关系数据库的分支事务的协调来驱动完成全局事务,是工做在应用层的中间件。主要优势是性能较好,且不长时间占用链接资源,它以高效而且对业务0侵入的方式解决微服务场景下面临的分布式事务问题,它目前提供AT模式(即2PC)及TCC模式的分布式事务解决方案。
Seata的设计思想以下:
Seata的设计目标其一是对业务无侵入,Seata把一个分布式事务理解成一个包含了若干分支事务的全局事务。全局事务的职责是协调其下管辖的分支事务达成一致,要么一块儿成功提交,要么一块儿失败回滚。,一般分支事务自己就是一个关系数据库的本地事务。
Seata定义了3个组件来协议分布式事务的处理过程:
Transaction Coordinator (TC): 事务协调器,它是独立的中间件,须要独立部署运行,它维护全局事务的运行状态,接收TM指令发起全局事务的提交与回滚,负责与RM通讯协调各各分支事务的提交或回滚。
Transaction Manager (TM): 事务管理器,TM须要嵌入应用程序中工做,它负责开启一个全局事务,并最终向TC发起全局提交或全局回滚的指令。
Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器TC的指令,驱动分支(本地)事务的提交和回滚。
seata客户端(RM、TM):spring-cloud-alibaba-seata-2.1.0.RELEASE
seata服务端(TC):seata-server-0.7.1
具体的执行流程以下:
1. 用户服务的 TM 向 TC 申请开启一个全局事务,全局事务建立成功并生成一个全局惟一的XID。
2. 用户服务的 RM 向 TC 注册 分支事务,该分支事务在用户服务执行新增用户逻辑,并将其归入 XID 对应全局
事务的管辖。
3. 用户服务执行分支事务,向用户表插入一条记录。
4. 逻辑执行到远程调用积分服务时(XID 在微服务调用链路的上下文中传播)。积分服务的RM 向 TC 注册分支事
务,该分支事务执行增长积分的逻辑,并将其归入 XID 对应全局事务的管辖。
5. 积分服务执行分支事务,向积分记录表插入一条记录,执行完毕后,返回用户服务。
6. 用户服务分支事务执行完毕。
7. TM 向 TC 发起针对 XID 的全局提交或回滚决议。
8. TC 调度 XID 下管辖的所有分支事务完成提交或回滚请求。
Seata实现2PC与传统2PC的差异:
架构层次方面,传统2PC方案的 RM 其实是在数据库层,RM 本质上就是数据库自身,经过 XA 协议实现,而Seata的 RM 是以jar包的形式做为中间件层部署在应用程序这一侧的。
两阶段提交方面,传统2PC不管第二阶段的决议是commit仍是rollback,事务性资源的锁都要保持到Phase2完成才释放。而Seata的作法是在Phase1 就将本地事务提交,这样就能够省去Phase2持锁的时间,总体提升效率。
****小结****
传统2PC(基于数据库XA协议)和Seata实现2PC的两种2PC方案,因为Seata的0侵入性而且解决了传统2PC长期锁资源的问题,因此推荐采用Seata实现2PC。
Seata实现2PC要点:
1、全局事务开始使用 @GlobalTransactional标识 。
2、每一个本地事务方案仍然使用@Transactional标识。
3、每一个数据都须要建立undo_log表,此表是seata保证本地事务一致性的关键
2)分布式事务解决方案之TCC
TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每一个分支事务实现三个操做:预处理Try、确认Confirm、撤销Cancel。Try操做作业务检查及资源预留,Confirm作业务确认操做,Cancel实现一个与Try相反的操做即回滚操做。TM首先发起全部的分支事务的try操做,任何一个分支事务的try操做执行失败,TM将会发起全部分支事务的Cancel操做,若try操做所有成功,TM将会发起全部分支事务的Confirm操做,其中Confirm/Cancel
操做若执行失败,TM会进行重试。
TCC分为三个阶段:
1. Try 阶段是作业务检查(一致性)及资源预留(隔离),此阶段仅是一个初步操做,它和后续的Confirm 一块儿才能真正构成一个完整的业务逻辑。
2. Confirm 阶段是作确认提交,Try阶段全部分支事务执行成功后开始执行 Confirm。一般状况下,采用TCC则认为 Confirm阶段是不会出错的。即:只要Try成功,Confirm必定成功。若Confirm阶段真的出错了,需引入重试机制或人工处理。
3. Cancel 阶段是在业务执行错误须要回滚的状态下执行分支事务的业务取消,预留资源释放。一般状况下,采用TCC则认为Cancel阶段也是必定成功的。若Cancel阶段真的出错了,需引入重试机制或人工处理。
4. TM事务管理器,TM事务管理器能够实现为独立的服务,也可让全局事务发起方充当TM的角色,TM独立出来是为了成为公用组件,是为了考虑系统结构和软件复用。
TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条,用来记录事务上下文,追踪和记录状态,因为Confirm 和cancel失败需进行重试,所以须要实现为幂等,幂等性是指同一个操做不管请求多少次,其结果都相同。
2.1 Hmily
是一个高性能分布式事务TCC开源框架。基于Java语言来开发(JDK1.8),支持Dubbo,Spring Cloud等RPC框架进行分布式事务。但Seata的TCC模式对Spring Cloud并无提供支持。它目前支持如下特性:
支持嵌套事务(Nested transaction support).
采用disruptor框架进行事务日志的异步读写,与RPC框架的性能毫无差异。
支持SpringBoot-starter 项目启动,使用简单。
RPC框架支持 : dubbo,motan,springcloud。
本地事务存储支持 : redis,mongodb,zookeeper,file,mysql。
事务日志序列化支持 :java,hessian,kryo,protostuff。
采用Aspect AOP 切面思想与Spring无缝集成,自然支持集群。
RPC事务恢复,超时异常恢复等。
Hmily利用AOP对参与分布式事务的本地方法与远程方法进行拦截处理,经过多方拦截,事务参与者能透明的调用到另外一方的Try、Confirm、Cancel方法;传递事务上下文;并记录事务日志,酌情进行补偿,重试等。
Hmily不须要事务协调服务,但须要提供一个数据库(mysql/mongodb/zookeeper/redis/file)来进行日志存储。
Hmily实现的TCC服务与普通的服务同样,只须要暴露一个接口,也就是它的Try业务。Confirm/Cancel业务逻辑,只是由于全局事务提交/回滚的须要才提供的,所以Confirm/Cancel业务只须要被Hmily TCC事务框架发现便可,不须要被调用它的其余业务服务所感知。
TCC须要注意三种异常处理分别是空回滚、幂等、悬挂:
空回滚:在没有调用 TCC 资源 Try 方法的状况下,调用了二阶段的 Cancel 方法,Cancel 方法须要识别出这是一个空回滚,而后直接返回成功。
出现缘由是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候实际上是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而造成空回滚。
解决思路是关键就是要识别出这个空回滚。思路很简单就是须要知道一阶段是否执行,若是执行了,那就是正常回滚;若是没执行,那就是空回滚。前面已经说过TM在发起全局事务时生成全局事务记录,全局事务ID贯穿整个分布式事务调用链条。再额外增长一张分支事务记录表,其中有全局事务 ID 和分支事务 ID,第一阶段 Try 方法里会插入一条记录,表示一阶段执行了。Cancel 接口里读取该记录,若是该记录存在,则正常回滚;若是该记录不存在,则是空回滚。
幂等:经过前面介绍已经了解到,为了保证TCC二阶段提交重试机制不会引起数据不一致,要求 TCC 的二阶段 Try、Confirm 和 Cancel 接口保证幂等,这样不会重复使用或者释放资源。若是幂等控制没有作好,颇有可能致使数据不一致等严重问题。
解决思路在上述“分支事务记录”中增长执行状态,每次执行前都查询该状态。
悬挂:悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。
出现缘由是在 RPC 调用分支事务try时,先注册分支事务,再执行RPC调用,若是此时 RPC 调用的网络发生拥堵,一般 RPC 调用是有超时时间的,RPC 超时之后,TM就会通知RM回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者真正执行,而一个 Try 方法预留的业务资源,只有该分布式事务才能使用,该分布式事务第一阶段预留的业务资源就再也没有人可以处理了,对于这种状况,咱们就称为悬挂,即业务资源预留后无法继续处理。
解决思路是若是二阶段执行完成,那一阶段就不能再继续执行。在执行一阶段事务时判断在该全局事务下,“分支事务记录”表中是否已经有二阶段事务记录,若是有则不执行Try。
拿TCC事务的处理流程与2PC两阶段提交作比较,2PC一般都是在跨库的DB层面,而TCC则在应用层面的处理,须要经过业务逻辑来实现。这种分布式事务的实现方式的优点在于,可让应用本身定义数据操做的粒度,使得下降锁冲突、提升吞吐量成为可能。而不足之处则在于对应用的侵入性很是强,业务逻辑的每一个分支都须要实现try、confirm、cancel三个操做。此外,其实现难度也比较大,须要按照网络状态、系统故障等不一样的失败缘由实现不一样的回滚策略。
1) 强引用,特色:咱们日常典型编码Object obj = new Object()中的obj就是强引用。经过关键字new建立的对象所关联的引用就是强引用。当JVM内存空间不足,JVM宁愿抛出OutOfMemoryError运行时错误(OOM),使程序异常终止,也不会靠随意回收具备强引用的“存活”对象来解决内存不足的问题。对于一个普通的对象,若是没有其余的引用关系,只要超过了引用的做用域或者显式地将相应(强)引用赋值为 null,就是能够被垃圾收集的了,具体回收时机仍是要看垃圾收集策略。
2))软引用,特色:软引用经过SoftReference类实现。 软引用的生命周期比强引用短一些。只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即JVM 会确保在抛出 OutOfMemoryError 以前,清理软引用指向的对象。
软引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,咱们能够调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。若是队列为空,将返回一个null,不然该方法返回队列中前面的一个Reference对象。
应用场景:软引用一般用来实现内存敏感的缓存。若是还有空闲内存,就能够暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
3 )弱引用,弱引用经过WeakReference类实现。 弱引用的生命周期比软引用短。
在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。因为垃圾回收器是一个优先级很低的线程,所以不必定会很快回收弱引用的对象。弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
应用场景:弱应用一样可用于内存敏感的缓存。
4) 虚引用:特色:虚引用也叫幻象引用,经过PhantomReference类来实现。没法经过虚引用访问对象的任何属性或函数。幻象引用仅仅是提供了一种确保对象被 finalize 之后,作某些事情的机制。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收器回收。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序能够经过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。若是程序发现某个虚引用已经被加入到引用队列,那么就能够在所引用的对象的内存被回收以前采起一些程序行动。
应用场景:可用来跟踪对象被垃圾回收器回收的活动,当一个虚引用关联的对象被垃圾收集器回收以前会收到一条系统通知。
== 能够做用于基本数据类型和引用数据类型
equals 只能够做用于引用数据类型
== 做用于基本数据类型比较的是基本数据类型的“值” 做用于引用数据类型比较的是地址
equals 目标对象没有重写equals()的方法的时候 比较的是对像的地址 重写了equals()比较 的是对象的内容
在 redis 中,容许用户设置最大使用内存大小 server.maxmemory,在内存限定的状况下是颇有用的。譬如,在一台 8G 机子上部署了 4 个 redis 服务点,每个服务点分配 1.5G 的内存大小,减小内存紧张的状况,由此获取更为稳健的服务。
内存大小有限,须要保存有效的数据?
redis 内存数据集大小上升到必定大小的时候,就会施行数据淘汰策略。
Redis提供了如下几种数据淘汰策略:
1、 volatile-lru:从设置过时的数据集中淘汰最少使用的数据;
2、volatile-ttl:从设置过时的数据集中淘汰即将过时的数据(离过时时间最近);
3、volatile-random:从设置过时的数据集中随机选取数据淘汰;
4、allkeys-lru:从全部 数据集中选取使用最少的数据;
5、allkeys-random:从全部数据集中任意选取数据淘汰;
6、no-envicition:不进行淘汰;
变屡次提交为一次,获取一次链接,执行屡次插入。在代码中使用循环插入数据,最后关闭链接。
像这样的批量插入操做能不使用代码操做就不使用,能够使用存储过程来实现。
Spring是什么?
spring是J2EE应用程序框架,是轻量级的IoC和AOP的容器框架(相对于重量级的EJB),主要是针对javaBean的生命周期进行管理的轻量级容器,能够单独使用,也能够和Struts框架,ibatis框架等组合使用。
1、IOC(Inversion of Control )或DI(Dependency Injection)
IOC控制权反转
原来:个人Service须要调用DAO,Service就须要建立DAO
Spring:Spring发现你Service依赖于dao,就给你注入.
核心原理:就是配置文件+反射(工厂也能够)+容器(map)
2、AOP:面向切面编程
核心原理:使用动态代理的设计模式在执行方法先后或出现异常作加入相关逻辑。
咱们主要使用AOP来作:
1、事务处理
2、权限判断
3、日志
答:Map接口和Collection接口是全部集合框架的父接口:
Collection接口的子接口包括:Set接口和List接口
Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的;
HashMap容许K/V都为null;后者K/V都不容许为null;
HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类;
下面先来分析一下源码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
// 1.若是table为空或者长度为0,即没有元素,那么使用resize()方法扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 2.计算插入存储的数组索引i,此处计算方法同 1.7 中的indexFor()方法
// 若是数组为空,即不存在Hash冲突,则直接插入数组
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 3.插入时,若是发生Hash冲突,则依次往下判断
else {
HashMap.Node<K,V> e; K k;
// a.判断table[i]的元素的key是否与须要插入的key同样,若相同则直接用新的value覆盖掉旧的value
// 判断原则equals() - 因此须要当key的对象重写该方法
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// b.继续判断:须要插入的数据结构是红黑树仍是链表
// 若是是红黑树,则直接在树中插入 or 更新键值对
else if (p instanceof HashMap.TreeNode)
e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 若是是链表,则在链表中插入 or 更新键值对
else {
// i .遍历table[i],判断key是否已存在:采用equals对比当前遍历结点的key与须要插入数据的key
// 若是存在相同的,则直接覆盖
// ii.遍历完毕后任务发现上述状况,则直接在链表尾部插入数据
// 插入完成后判断链表长度是否 > 8:如果,则把链表转换成红黑树
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 对于i 状况的后续操做:发现key已存在,直接用新value覆盖旧value&返回旧value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
// 插入成功后,判断实际存在的键值对数量size > 最大容量
// 若是大于则进行扩容
if (++size > threshold)
resize();
// 插入成功时会调用的方法(默认实现为空)
afterNodeInsertion(evict);
return null;
}
简单总结为:
1、put(key, value)中直接调用了内部的putVal方法,而且先对key进行了hash操做;
2、putVal方法中,先检查HashMap数据结构中的索引数组表是否位空,若是是的话则进行一次resize操做;
3、以HashMap索引数组表的长度减一与key的hash值进行与运算,得出在数组中的索引,若是索引指定的位置值为空,则新建一个k-v的新节点;
4、若是不知足的3的条件,则说明索引指定的数组位置的已经存在内容,这个时候称之碰撞出现;
5、在上面判断流程走完以后,计算HashMap全局的modCount值,以便对外部并发的迭代操做提供修改的Fail-fast判断提供依据,于此同时增长map中的记录数,并判断记录数是否触及容量扩充的阈值,触及则进行一轮resize操做;
6、在步骤4中出现碰撞状况时,从步骤7开始展开新一轮逻辑判断和处理;
7、判断key索引到的节点(暂且称做被碰撞节点)的hash、key是否和当前待插入节点(新节点)的一致,若是是一致的话,则先保存记录下该节点;若是新旧节点的内容不一致时,则再看被碰撞节点是不是树(TreeNode)类型,若是是树类型的话,则按照树的操做去追加新节点内容;若是被碰撞节点不是树类型,则说明当前发生的碰撞在链表中(此时链表还没有转为红黑树),此时进入一轮循环处理逻辑中;
8、循环中,先判断被碰撞节点的后继节点是否为空,为空则将新节点做为后继节点,做为后继节点以后并判断当前链表长度是否超过最大容许链表长度8,若是大于的话,须要进行一轮是否转树的操做;若是在一开始后继节点不为空,则先判断后继节点是否与新节点相同,相同的话就记录并跳出循环;若是两个条件判断都知足则继续循环,直至进入某一个条件判断而后跳出循环;
9、步骤8中转树的操做treeifyBin,若是map的索引表为空或者当前索引表长度还小于64(最大转红黑树的索引数组表长度),那么进行resize操做就好了;不然,若是被碰撞节点不为空,那么就顺着被碰撞节点这条树日后新增该新节点;
10、最后,回到那个被记住的被碰撞节点,若是它不为空,默认状况下,新节点的值将会替换被碰撞节点的值,同时返回被碰撞节点的值(V)。
HashMap经过resize()方法进行扩容或者初始化的操做,下面是对源码进行的一些简单分析:
/**
* 该函数有2中使用状况:1.初始化哈希表;2.当前数组容量太小,须要扩容
*/
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;// 扩容前的数组(当前数组)
int oldCap = (oldTab == null) ? 0 : oldTab.length;// 扩容前的数组容量(数组长度)
int oldThr = threshold;// 扩容前数组的阈值
int newCap, newThr = 0;
if (oldCap > 0) {
// 针对状况2:若扩容前的数组容量超过最大值,则再也不扩容
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 针对状况2:若没有超过最大值,就扩容为原来的2倍(左移1位)
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 针对状况1:初始化哈希表(采用指定或者使用默认值的方式)
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
// 计算新的resize上限
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 把每个bucket都移动到新的bucket中去
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认状况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫作临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,而后从新计算每一个元素在数组中的位置。
0.75这个值成为负载因子,那么为何负载因子为0.75呢?这是经过大量实验统计得出来的,若是太小,好比0.5,那么当存放的元素超过一半时就进行扩容,会形成资源的浪费;若是过大,好比1,那么当元素满的时候才进行扩容,会使get,put操做的碰撞概率增长。
能够看到HashMap不是无限扩容的,当达到了实现预约的MAXIMUM_CAPACITY,就再也不进行扩容。
咱们首先须要知道什么是哈希冲突,而在了解哈希冲突以前咱们还要知道什么是哈希才行;
什么是哈希?
Hash,通常翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入经过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间一般远小于输入的空间,不一样的输入可能会散列成相同的输出,因此不可能从散列值来惟一的肯定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
全部散列函数都有以下一个基本特性:根据同一散列函数计算出的散列值若是不一样,那么输入值确定也不一样。可是,根据同一散列函数计算出的散列值若是相同,输入值不必定相同。
什么是哈希冲突?
当两个不一样的输入值,根据同一散列函数计算出相同的散列值的现象,咱们就把它叫作碰撞(哈希碰撞)。
HashMap的数据结构
在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特色是:寻址容易,插入和删除困难;链表的特色是:寻址困难,但插入和删除容易;因此咱们将数组和链表结合在一块儿,发挥二者各自的优点,使用一种叫作链地址法的方式能够解决哈希冲突:
这样咱们就能够将拥有相同哈希值的对象组织成一个链表放在hash值所对应的bucket下,但相比于hashCode返回的int类型,咱们HashMap初始的容量大小DEFAULT_INITIAL_CAPACITY = 1 << 4(即2的四次方16)要远小于int类型的范围,因此咱们若是只是单纯的用hashCode取余来获取对应的bucket这将会大大增长哈希碰撞的几率,而且最坏状况下还会将HashMap变成一个单链表,因此咱们还须要对hashCode做必定的优化
hash()函数
上面提到的问题,主要是由于若是使用hashCode取余,那么至关于参与运算的只有hashCode的低位,高位是没有起到任何做用的,因此咱们的思路就是让hashCode取值出的高位也参与运算,进一步下降hash碰撞的几率,使得数据分布更平均,咱们把这样的操做称为扰动
在JDK 1.8中的hash()函数以下:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// 与本身右移16位进行异或运算(高低位异或)
}
这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动);
JDK1.8新增红黑树
经过上面的链地址法(使用散列表)和扰动函数咱们成功让咱们的数据分布更平均,哈希碰撞减小,可是当咱们的HashMap中存在大量数据时,加入咱们某个bucket下对应的链表有n个元素,那么遍历时间复杂度就为O(n),为了针对这个问题,JDK1.8在HashMap中新增了红黑树的数据结构,进一步使得遍历复杂度下降至O(logn);
总结
简单总结一下HashMap是使用了哪些方法来有效解决哈希冲突的:
1. 使用链地址法(使用散列表)来连接拥有相同hash值的数据;
2. 使用2次扰动函数(hash函数)来下降哈希冲突的几率,使得数据分布更平均;
3. 引入红黑树进一步下降遍历的时间复杂度,使得遍历更快;
hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap一般状况下是取不到最大值的,而且设备上也难以提供这么多的存储空间,从而致使经过hashCode()计算出的哈希值可能不在数组大小范围内,进而没法匹配存储位置;
那怎么解决呢?
HashMap本身实现了本身的hash()方法,经过两次扰动使得它本身的哈希值高低位自行进行异或运算,下降哈希碰撞几率也使得数据分布更平均;
在保证数组长度为2的幂次方的时候,使用hash()运算以后的值与运算(&)(数组长度 - 1)来获取数组下标的方式进行存储,这样一来是比取余操做更加有效率,二来也是由于只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三来解决了“哈希值与数组大小范围不匹配”的问题;
只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,即实现了key的定位,2的幂次方也能够减小冲突次数,提升HashMap的查询效率;
若是 length 为 2 的次幂 则 length-1 转化为二进制一定是 11111……的形式,在于 h 的二进制与操做效率会很是的快,并且空间不浪费;若是 length 不是 2 的次幂,好比 length 为 15,则 length - 1 为 14,对应的二进制为 1110,在于 h 与操做,最后一位都为 0 ,而 0001,0011,0101,1001,1011,0111,1101 这几个位置永远都不能存放元素了,空间浪费至关大,更糟的是这种状况中,数组能够使用的位置比数组长度小了不少,这意味着进一步增长了碰撞的概率,减慢了查询的效率!这样就会形成空间的浪费。
那为何是两次扰动呢?
答:这样就是加大哈希值低位的随机性,使得分布更均匀,从而提升对应数组存储下标位置的随机性&均匀性,最终减小Hash冲突,两次就够了,已经达到了高位低位同时参与运算的目的;
不一样 |
JDK 1.7 |
JDK 1.8 |
存储结构 |
数组 + 链表 |
数组 + 链表 + 红黑树 |
初始化方式 |
单独函数: |
直接集成到了扩容函数 |
hash值计算方式 |
扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算 |
扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算 |
存放数据的规则 |
无冲突时,存放数组;冲突时,存放链表 |
无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树 |
插入数据方式 |
头插法(先讲原位置的数据移到后1位,再插入数据到该位置) |
尾插法(直接插入到链表尾部/红黑树) |
扩容后存储位置的计算方式 |
所有按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)) |
按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量) |
String、Integer等包装类的特性可以保证Hash值的不可更改性和计算准确性,可以有效的减小Hash碰撞的概率都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不一样的状况。内部已重写了equals()、hashCode()等方法,遵照了HashMap内部的规范(不清楚能够去上面看看putValue的过程),不容易出现Hash值计算错误的状况;
面试官:若是我想要让本身的Object做为K应该怎么办呢?
重写hashCode()和equals()方法
重写hashCode()是由于须要计算存储数据的存储位置,须要注意不要试图从散列码计算中排除掉一个对象的关键部分来提升性能,这样虽然能更快但可能会致使更多的Hash碰撞;
重写`equals()`方法,须要遵照自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的惟一性;
ConcurrentHashMap 结合了 HashMap 和 HashTable 两者的优点。HashMap 没有考虑同步,HashTable 考虑了同步的问题。可是 HashTable 在每次同步执行时都要锁住整个结构。ConcurrentHashMap 锁的方式是稍微细粒度的。
面试官:ConcurrentHashMap的具体实现知道吗?
答:在JDK1.7中,ConcurrentHashMap采用Segment + HashEntry的方式进行实现,结构以下:
1、该类包含两个静态内部类 HashEntry 和 Segment ;前者用来封装映射表的键值对,后者用来充当锁的角色;
2、Segment 是一种可重入的锁 ReentrantLock,每一个 Segment 守护一个HashEntry 数组里得元素,当对 HashEntry 数组的数据进行修改时,必须首先得到对应的 Segment 锁。
在JDK1.8中,放弃了Segment臃肿的设计,取而代之的是采用Node + CAS + Synchronized来保证并发安全进行实现,结构以下:
1、若是相应位置的Node尚未初始化,则调用CAS插入相应的数据;
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
2、若是相应位置的Node不为空,且当前该节点不处于移动状态,则对该节点加synchronized锁,若是该节点的hash不小于0,则遍历链表更新节点或插入新节点;
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key, value, null);
break;
}
}
}
3、若是该节点是TreeBin类型的节点,说明是红黑树结构,则经过putTreeVal方法往红黑树中插入节点;若是binCount不为0,说明put操做对数据产生了影响,若是当前链表的个数达到8个,则经过treeifyBin方法转化为红黑树,若是oldVal不为空,说明是一次更新操做,没有对元素个数产生影响,则直接返回旧值;
4、若是插入的是一个新节点,则执行addCount()方法尝试更新元素个数baseCount;
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操做时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程1、线程2),线程1经过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
缘由:迭代器在遍历时直接访问集合中的内容,而且在遍历过程当中使用一个 modCount 变量。集合在被遍历期间若是内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素以前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;不然抛出异常,终止遍历。
解决办法:
1. 在遍历过程当中,全部涉及到改变modCount值得地方所有加上synchronized。
2. 使用CopyOnWriteArrayList来替换ArrayList
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素位置都是有顺序的,至关于一种动态的数组,咱们之后能够按位置索引来取出某个元素,而且其中的数据是容许重复的,这是与 HashSet 之类的集合的最大不一样处,HashSet 之类的集合不能够按索引号去检索其中的元素,也不容许有重复的元素。
ArrayList 与 Vector 的区别主要包括两个方面:
同步性:
Vector 是线程安全的,也就是说它的方法之间是线程同步(加了synchronized 关键字)的,而 ArrayList 是线程不安全的,它的方法之间是线程不一样步的。若是只有一个线程会访问到集合,那最好是使用 ArrayList,由于它不考虑线程安全的问题,因此效率会高一些;若是有多个线程会访问到集合,那最好是使用 Vector,由于不须要咱们本身再去考虑和编写线程安全的代码。
数据增加:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的我的超过了容量时,就须要增长 ArrayList 和 Vector 的存储空间,每次要增长存储空间时,不是只增长一个存储单元,而是增长多个存储单元,每次增长的存储单元的个数在内存空间利用与程序效率之间要去的必定的平衡。Vector 在数据满时(加载因子1)增加为原来的两倍(扩容增量:原容量的 2 倍),而 ArrayList 在数据量达到容量的一半时(加载因子 0.5)增加为原容量的 (0.5 倍 + 1) 个空间。
LinkedList 实现了 List 和 Deque 接口,通常称为双向链表;ArrayList 实现了 List 接口,动态数组;
LinkedList 在插入和删除数据时效率更高,ArrayList 在查找某个 index 的数据时效率更高;
LinkedList 比 ArrayList 须要更多的内存;
面试官:Array 和 ArrayList 有什么区别?何时该应 Array 而不是 ArrayList 呢?
它们的区别是:
Array 能够包含基本类型和对象类型,ArrayList 只能包含对象类型。
Array 大小是固定的,ArrayList 的大小是动态变化的。
ArrayList 提供了更多的方法和特性,好比:addAll(),removeAll(),iterator() 等等。
对于基本类型数据,集合使用自动装箱来减小编码工做量。可是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
HashSet的底层其实就是HashMap,只不过咱们HashSet是实现了Set接口而且把数据做为K值,而V值一直使用一个相同的虚值来保存,咱们能够看到源码:
public boolean add(E e) {
return map.put(e, PRESENT)==null;// 调用HashMap的put方法,PRESENT是一个至始至终都相同的虚值
}
因为HashMap的K值自己就不容许重复,而且在HashMap中若是K/V相同时,会用新的V覆盖掉旧的V,而后返回旧的V,那么在HashSet中执行这一句话始终会返回一个false,致使插入失败,这样就保证了数据的不可重复性;
Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。咱们不须要担忧等待生产者有可用的空间,或消费者有可用的对象,由于它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,好比ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。
Map是以键值对来存储对象的,它的底层其实是数组和链表
当使用put方法时,先查找出数组位置是否存在对象,经过key.hashcode对数组长度取余;存在,则把里面的链表拿出来,判断链表里面是否存在key值与传递过来的key值同样的对象,存在,则把传递过来的value取代链表key对应的value,不存在,则直接经过链表的add()方法加到链表后面;
当使用get方法时,先查找出数组位置是否存在对象,经过key.hashcode对数组长度取余;若是不存在,则返回为空,若是存在,则遍历链表,判断链表里面是否存在key值与传递过来的key值同样的对象,存在,则把key值对应的value取出返回,不存在,则返回为空;
评注:此题为走向题,你的回答不一样,后面问题走向就变了。
关于容量:单表行数超过 500 万行或者单表容量超过2GB,此时就要答分库分表的中间件了!那后面题目的走向就变为mycat、sharing-jdbc等分库分表中间件的底层原理了!
关于并发量:若是并发数过1200,此时就要答利用MQ或者redis等中间件,做为补偿措施,而不能直接操做数据库。那后面的题目走向就是redis、mq的原理了!
介于面试者仍是一个应届生,我斗胆猜想面试者是这么答的
回答:数据量估计就三四百万吧,并发量就五六百左右!
从数据结构角度:
B-Tree索引,数据结构就是一颗B+树。
Hash索引,Hash索引比较的是进行Hash运算以后的Hash值,因此它只能用于等值的过滤,不能用于基于范围的过滤。基本不用!
R-Tree索引,仅支持geometry数据类型,也基本不用!
至于非主键的二级索引,这个实际上问的就是非聚簇索引!非聚簇索引自己就是一颗B+树,其根节点指向聚簇索引的B+树,具体的请看这篇文章《MySQL(Innodb)索引的原理》
• Spring Boot能够创建独立的Spring应用程序;
• 内嵌了如Tomcat,Jetty和Undertow这样的容器,也就是说能够直接跑起来,用不着再作部署工做了。
• 无需再像Spring那样搞一堆繁琐的xml文件的配置;
• 能够自动配置Spring;
• 提供了一些现有的功能,如量度工具,表单数据验证以及一些外部配置这样的一些第三方功能;
• 提供的POM能够简化Maven的配置
先答为何须要自动配置?
顾名思义,自动配置的意义是利用这种模式代替了配置 XML 繁琐模式。之前使用 Spring MVC ,须要进行配置组件扫描、调度器、视图解析器等,使用 Spring Boot 自动配置后,只须要添加 MVC 组件便可自动配置所须要的 Bean。全部自动配置的实现都在 spring-boot-autoconfigure 依赖中,包括 Spring MVC 、Data 和其它框架的自动配置。
接着答spring-boot-autoconfigure 依赖的工做原理?
spring-boot-autoconfigure 依赖的工做原理很简单,经过 @EnableAutoConfiguration 核心注解初始化,并扫描 ClassPath 目录中自动配置类对应依赖。好比工程中有木有添加 Thymeleaf 的 Starter 组件依赖。若是有,就按按必定规则获取默认配置并自动初始化所须要的 Bean。
其实还能再继续答@EnableAutoConfiguration 注解的工做原理!不过篇幅太长,答到上面那个地步就够了!
回答:一共五步
• 1. Mapper 接口在初始SqlSessionFactory 注册的。
• 2. Mapper 接口注册在了名为 MapperRegistry 类的 HashMap中, key = Mapper class value = 建立当前Mapper的工厂。
• 3. Mapper 注册以后,能够从SqlSession中get
• 4. SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。
• 5. 动态代理的 代理类是 MapperProxy ,这里边最终完成了增删改查方法的调用。
JVM内存结构主要有三大块:堆内存、方法区和栈。堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分红三部分,Eden空间、From Survivor空间、To Survivor空间,默认状况下年轻代按照8:1:1的比例来分配;
方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。
堆内存是JVM中最大的一块由年轻代和老年代组成。
那么,从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。
Major GC 是清理老年代。
Full GC 是清理整个堆空间—包括年轻代和老年代。
标记-清除算法、标记整理算法、复制算法、分代收集算法
评注:上面的题目更深刻的问法。JVM能够配置不一样的回收器。好比Serial, Parallel和CMS几种垃圾回收器。以Serial Collector(串行回收器)为例,它在在年轻代是一个使用标记-复制算法的回收器。在老年代使用的是标记-清扫-整理算法。
另外,关于G1回收器能够问的点不少,此题做者没有描述清楚究竟问的是G1回收器的那个点,就满回答一下概念吧!
若是是我来问,我就直接给你场景,问你该用哪一种回收器了。直接问回收器,那就比较容易了!
经常使用参数:
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:+UseParNewGC:在新生代使用并行收集器
//本身查询吧,太多了!
回答:
G1 GC是Jdk7的新特性之1、Jdk7+版本均可以自主配置G1做为JVM GC选项。G1 将整个堆划分为一个个大小相等的小块(每一块称为一个region),每一块的内存是连续的,每一个块也会充当 Eden、Survivor、Old三种角色,可是它们不是固定的,这使得内存使用更加地灵活。以下图所示
执行垃圾收集时,收集线程在标记阶段和应用程序线程并发执行,标记结束后,G1 也就知道哪些区块基本上是垃圾,存活对象极少,G1 会先从这些区块下手,由于从这些区块能很快释放获得很大的可用空间,这也是为何 G1 被取名为 Garbage-First 的缘由。
其实RestTemplate和sl4fj这种门面框架很像,本质就是在Http的网络请求中增长一个马甲,自己并无本身的实现。对此有疑问的,能够看个人另外一篇
《架构师必备,带你弄清混乱的JAVA日志体系!》
底层能够支持多种httpclient的http访问,上层为ClientHttpRequestFactory接口类,底层以下所示:
那么RestTemplate则封装了组装、发送 HTTP消息,以及解析响应的底层细节。
利用DNS进行域名解析 --> 发起TCP的3次握手 --> 创建TCP链接后发起http请求 --> 服务器响应http请求,浏览器获得html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户
经常使用方法:
start,run,sleep,wait,notify,notifyAll,join,isAlive,currentThread,interrupt
经常使用接口类:
Runnable、Callable、Future、FutureTask
在面向对象编程中,建立和销毁对象是很费时间的,由于建立一个对象要获取内存资源或者其它更多资源。因此提升服务程序效率的一个手段就是尽量减小建立和销毁对象的次数,因此出现了池化技术!。
简单的线程池包括以下四个组成部分便可:
• 线程池管理器(ThreadPoolManager):用于建立并管理线程池
• 工做线程(WorkThread): 线程池中线程
• 任务接口(Task):每一个任务必须实现的接口,以供工做线程调度任务的执行
• 任务队列:用于存放没有处理的任务。提供一种缓冲机制
死锁是指两个或两个以上的进程在执行过程当中,因争夺资源而形成的一种互相等待的现象,若无外力做用,它们都将没法推动下去,若是系统资源充足,进程的资源请求都可以获得知足,死锁出现的可能性就很低,不然就会因争夺有限的资源而陷入死锁。
产生死锁的缘由主要是:
• (1) 由于系统资源不足。
• (2) 进程运行推动的顺序不合适。
• (3) 资源分配不当等。
只要是远程调用均可以叫RPC,和是否是经过http没什么关系。
那么,调用过程,也就是通讯过程之间须要协议,能够是HTTP协议、dubbo协议等、其余协议等。
81、什么是降级
在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。
降级的最终目的是保证核心服务可用,即便是有损的。并且有些服务是没法降级的(如加入购物车、结算)。
starter中简单来说就是引入了一些相关依赖和一些初始化的配置。
为何加了@Configuration注解仍是要配置META-INF/spring.factories呢?由于springboot项目默认只会扫描本项目下的带@Configuration注解的类,若是自定义starter,不在本工程中,是没法加载的,因此要配置META-INF/spring.factories配置文件,这个由@EnableAutoConfiguration帮咱们实现。
volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized一般称为重量级锁),volatile更轻量级,由于它不会引发线程上下文的切换和调度。可是volatile 变量的同步性较差(有时它更简单而且开销更低),并且其使用也更容易出错。
(1)保证可见性,不保证原子性
a.当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
b.这个写会操做会致使其余线程中的缓存无效。
1)volatile不适合复合操做
(2)禁止指令重排
1)原子性
定义: 即一个操做或者多个操做 要么所有执行而且执行的过程不会被任何因素打断,要么就都不执行。
原子性是拒绝多线程操做的,不管是多核仍是单核,具备原子性的量,同一时刻只能有一个线程来对它进行操做。简而言之,在整个操做过程当中不会被线程调度器中断的操做,均可认为是原子性。例如 a=1是原子性操做,可是a++和a +=1就不是原子性操做。Java中的原子性操做包括:
a. 基本类型的读取和赋值操做,且赋值必须是数字赋值给变量,变量之间的相互赋值不是原子性操做。
b.全部引用reference的赋值操做
c.java.concurrent.Atomic.* 包中全部类的一切操做
(2)可见性
定义:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程可以当即看获得修改的值。
在多线程环境下,一个线程对共享变量的操做对其余线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会当即被更新到主内存中,其余线程读取共享变量时,会直接从主内存中读取。固然,synchronize和Lock均可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁而后执行同步代码,而且在释放锁以前会将对变量的修改刷新到主存当中。所以能够保证可见性。
(3)有序性
定义:即程序执行的顺序按照代码的前后顺序执行。
Java内存模型中的有序性能够总结为:若是在本线程内观察,全部操做都是有序的;若是在一个线程中观察另外一个线程,全部操做都是无序的。
XA是X/Open组织为DTP(分布式事务处理)制定的标准协议。XA的目的是保证分布式事务的ACID特性,就像本地事务同样。