工作久了就会发现,基础知识忘得差不多了。为了复习下基础的知识,同时为以后找工作做准备,这里简单总结一些常见的可能会被问到的问题。
自己根据实际情况发挥就行
集合类图:
集合的顶层接口,常见的主要有: Collection接口
、Map接口
和Collection相关的接口主要有: Collection
、List
和Set
接口
Collection接口
Collection是一个抽象出来的接口
,定义如下:
public interface Collection extends Iterable {}
其中包括了集合的基本操作,包括: 删除
、添加
、遍历
、大小
等
List接口
List接口继承自Collection,List中的元素的有序且允许重复的
。定义如下:
public interface List extends Collection {}
Set接口
Set接口继承自Collection,Set是数学中定义的集合,元素无需不允许重复
。定义如下:
public interface Set extends Collection {}
Map接口
,也是顶层接口,Map保存的是键值对映射,映射关系可以是一对一或者一对多。
参考资料: https://blog.csdn.net/weixin_34176694/article/details/88708182
Collection是集合类的顶级接口,继承与他的接口主要有List和Set。而Collections是针对集合类的一个工具类,它提供了一系列的静态方法实现对各种集合的搜索、排序、线程安全化等操作。
ArrayList 和Vector底层都是使用数组方式存储数据
,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢
,Vector由于使用了synchronized方法(线程安全),通常性能上较ArrayList差,而LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入速度较快
。
请说明ArrayList和LinkedList的区别?
ArrayList和LinkedList都实现了List接口,ArrayList是基于索引的数据接口,它的底层是数组。它可以以O(1)时间复杂度
对元素进行随机访问。而LinkedList是以元素列表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)
。LinkedList的插入,添加,删除操作比ArrayList速度更快,因为当元素被添加到集合任意位置的时候,不需要像数组那样重新计算大小或者是更新索引。LinkedList比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用,一个指向前一个元素,一个指向下一个元素。
HashTable
底层数组+链表实现,无论key还是value都不能为null,线程安全,适合于多线程环境
,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低
,ConcurrentHashMap做了相关优化
初始size为11,扩容:newsize = olesize*2+1
计算index的方法:index = (hash & 0x7FFFFFFF) % tab.length
HashMap
底层数组+链表实现,可以存储null键和null值,线程不安全,更适合于单线程环境
初始size为16,扩容:newsize = oldsize*2,size一定为2的n次幂
扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入元素后才判断该不该扩容,有可能无效扩容(插入后如果扩容,如果没有再次插入,就会产生无效扩容)
当Map中元素总数超过Entry数组的75%,触发扩容操作,为了减少链表长度,元素分配更均匀
计算index方法:index = hash & (tab.length – 1)
注:
在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现
,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用位桶+链表+红黑树实现
,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
更详细的介绍,可参考我的另一篇博客: HashMap、Hashtable、ConcurrentHashMap的区别和原理浅析
相同点
Hashtable和ConcurrentHashMap都实现了Map接口,两者都是线程安全的
不同点
Java5提供了ConcurrentHashMap,它是HashTable的替代,ConcurrentHashMap比HashTable的扩展性更好,性能也大大提升了
为什么说ConcurrentHashMap性能大大提高了?
简单来说,ConcurrentHashMap底层有分段锁,类似于mysql中的行级锁,而Hashtable是锁整个hash表,类似于mysql中的表级锁
ConcurrentHashMap提供了与Hashtable和SynchronizedMap不同的锁机制。Hashtable中采用的锁机制是一次锁住整个hash表,从而在同一时刻只能由一个线程对其进行操作,而ConcurrentHashMap是使用了锁分段技术来保证线程安全的
,是一次锁住一个桶。
ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。
锁分段技术: 首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
。
底层采用分段的数组+链表实现,是线程安全的
通过把整个Map分为N个Segment,可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。(读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值。)
Hashtable的synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术
有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁
扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,可以有效避免无效扩容
SpringBoot
是一个快速开发框架,通过用Maven依赖的继承方式,帮助我们快速整合第三方常用框架
。比起传统的Spring,SpringBoot采用注解化的方式(使用注解方式启动SpringMVC),简化Xml配置,内置HTTP服务器(Tomcat,Jetty),最终以Java应用程序进行执行
。
SpringCloud
SpringCloud是一套目前比较完整的微服务框架,是分布式微服务架构下的一站式解决方案
,是各个微服务架构落地技术的结合体,俗称为微服务全家桶
。
它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过SpringBoot风格进行再次封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂
、易部署
和易维护
的分布式系统开发工具包
。它基于SpringBoot提供了一套微服务(microservices)解决方案,包括服务注册与发现
、配置中心
、服务网关
、全链路监控
、负载均衡
、熔断断路器
等组件,都可以用SpringBoot的开发风格做到一键启动和部署。
SpringBoot只是一个快速开发框架,使用注解大大简化了Spring的Xml配置,内置了Servlet容器,以Java应用程序进行执行。
SpringBoot专注于开发单应用微服务,而SpringCloud是一系列框架的集合,SpringCloud是微服务协调治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来
。
SpringBoot可以离开SpringCloud单独使用,而Spring Cloud很大的一部分是基于SpringBoot来实现的,所以说SpringCloud离不开SpringBoot
Spring 是 个 java 企 业 级 应 用 的 开 源 开 发 框 架
。 Spring 主 要 用 来 开 发 Java 应 用 , 但 是 有 些 扩 展 是 针 对 构 建 J2EE 平 台 的 web 应用。Spring 框 架 目 标 是 简 化 Java 企 业 级 应 用 开 发 , 并 通 过 POJO 为 基 础 的 编 程 模 型 促 进 良 好 的 编 程 习 惯 。
Transactional 注解的属性:
Spring在TransactionDefinition接口中定义这些属性,定义了五个不同的事务隔离级别:
Isolation 属性一共支持五种事务设置:
表格总结如下:
类型
解释
ISOLATION_DEFAULT
这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别,另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED
这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读
ISOLATION_READ_COMMITTED
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读
ISOLATION_REPEATABLE_READ
这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读
ISOLATION_SERIALIZABLE
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行,除了防止脏读,不可重复读外,还避免了幻像读
事务传播行为指的是: 两个有事务的方法进行相互调用的时候,它的事务如何进行传播
在TransactionDefinition接口中定义了七个事务传播行为:
PROPAGATION_REQUIRED
如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务PROPAGATION_REQUIRES_NEW
总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起PROPAGATION_SUPPORTS
如果存在一个事务,支持当前事务。如果没有事务,则以非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同PROPAGATION_NOT_SUPPORTED
总是非事务地执行,并挂起任何存在的事务PROPAGATION_NEVER
总是非事务地执行,如果存在一个活动事务,则抛出异常PROPAGATION_MANDATORY
如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常PROPAGATION_NESTED
如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按TransactionDefinition.PROPAGATION_REQUIRED 属性执行propagation 属性(事务传播性)表格整理如下:
类型
解释
REQUIRED
默认的传播行为,REQUIRED 支持当前已经存在的事务,如果还没有事务,就创建一个新事务
REQUIRES_NEW
如果当前有事务,挂起当前事务,创建一个新事务,如果还没有事务,就简单地创建一个新事务
SUPPORTS
支持当前事务,有事务就加入事务,如果没有事务,那么就以非事务的方式运行
NOT_SUPPORTED
强制不在事务中运行,如果当前存在一个事务,则挂起该事务
NEVER
强制要求不在事务中运行,如果当前存在一个事务,则抛出异常
MANDATORY
支持当前已经存在的事务,如果还没有事务,就抛出一个异常
NESTED
在当前事务中创建一个嵌套事务,如果还没有事务,那么就简单地创建一个新事务
Spring支持编程式事务管理
和声明式事务管理
两种方式
编程式事务
编程式事务使用TransactionTemplate
或者直接使用底层的PlatformTransactionManager
。对于编程式事务管理,Spring推荐使用TransactionTemplate
声明式事务
声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
声明式事务管理使业务代码不受污染,显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。通常,使用注解方式通过@Transactional
来使用事务。我们可以使用@EnableTransactionManagement
注解来启用事务管理功能,该注解可以加在启动类上或者单独加个配置类来处理。@Transactional
可以加在方法上
,表示对当前方法配置事务。也可以添加到类级别上
,当把@Transactional 注解放在类级别时,表示所有该类的公共方法都配置相同的事务属性信息。
项目中是使用注解方式实现的事务,这里以这种场景进行说明:
Spring事务是使用了@Transactional注解,底层产生CGLIB动态代理对象来进行事物的管理
。如果一个service类没有产生动态代理对象的时候,那么它不会对事务进行管理。
声明式事务是建立在AOP之上的,通过注解方式或者是在xml文件中进行配置。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务
。
参考资料: https://www.cnblogs.com/kingsonfu/p/10413154.html
前端控制器DispatcherServlet捕获
HandlerExecutionChain
对象的形式返回HandlerAdapter
(附注:如果成功获得HandlerAdapter后,此时将开始执行拦截器的preHandler(…)方法)ModelAndView
对象ViewResolver
(必须是已经注册到Spring容器中的ViewResolver)返回给DispatcherServletAOP(Aspect-OrientedProgramming),即面向切面编程。它与OOP(Object-OrientedProgramming,面向对象编程)相辅相成,提供了与OOP不同的抽象软件结构的视角。在OOP中,我们以类(class)作为我们的基本单元,而AOP中的基本单元是Aspect(切面)。
AOP核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能
。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在SpringAOP中,切面通过带有@Aspect注解的类实现。
Aspect由pointcount切入点
和advice通知
组成,它既包含了横切逻辑的定义,也包括了连接点的定义。SpringAOP就是负责实施切面的框架,它将切面所定义的横切逻辑编织到切面所指定的连接点中。AOP的工作重心在于如何将增强编织到目标对象的连接点上
,这里包含两个工作:
可以简单地认为,使用@Aspect注解的类就是切面
连接点代表一个应用程序的某个位置,在这个位置我们可以插入一个AOP切面,它实际上是个应用程序执行Spring AOP的位置。
比如我在我的业务方法上加入我的切面注解,进行日志切面处理:
/**
*
* 自定义日志存储注解
*
*
* @author smilehappiness
* @Date 2021/8/15 10:08
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperateLog {
/**
* 日志操作信息
*/
String value() default "";
}
@OperateLog("切面日志处理")
@PostMapping(value = "/path1")
public Result
切入点JoinPoint
是一个或一组连接点,是程序运行中的一些时间点,通知将在这些位置执行,可以通过表达式或匹配的方式指明切入点,例如一个方法的执行,或者是一个异常的处理。在Spring AOP中,joinpoint总是方法的执行点。
例如:
/**
* 使用@Pointcut定义切点,在加注解的位置切入代码
*/
@Pointcut("@annotation(com.smilehappiness.aspect.operate.OperateLog)")
public void logPointCut() {
}
Advice概念
特定JoinPoint处的Aspect所采取的动作称为Advice,通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。
。SpringAOP使用一个Advice作为拦截器,在JoinPoint“周围”维护一系列的拦截器。
有哪些类型的通知(Advice)
Spring切面可以应用五种类型的通知:
切面通知类型
解释
Before
前置通知
,在一个方法执行前被调用,该类型的Advice在joinpoint方法之前执行,使用@Before注解标记进行配置
After
后置通知
,在连接点方法执行之后调用的通知,无论方法执行是否成功,使用@After注解标记进行配置
Around
环绕通知
,在连接点方法执行之前和之后调用的通知,使用@Around注解标记进行配置。目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,joinPoint.procced()就是执行目标方法的代码 。环绕通知可以控制返回对象
AfterReturning
返回通知
,在连接点方法正常执行后执行,仅当方法成功完成后执行的通知,使用@AfterReturning注解标记进行配置
AfterThrowing
异常通知
,仅在joinpoint方法抛出异常退出时执行的通知,使用@AfterThrowing注解标记配置时执行
关注点-concern是我们想要在应用程序的特定模块中定义的行为,一个关注点可能会被定义成一个我们想要实现的一个功能。横切关注点-cross-cuttingconcern是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用程序
,比如日志
,安全性
和数据传输
,是应用程序几乎每个模块都需要关注的问题,因此它们是跨领域的问题,这些都属于横切关注点。
Weaving编织也叫做织入,为了创建一个advice通知对象而链接一个aspect和其它应用类型或对象的过程。织入可以在编译时,加载时,或运行时完成
,比如说,在SpringAOP中,编织在运行时执行。
简单来说,织入是将切面和其他应用类型或对象连接或创建一个被通知对象的过程
。
实现AOP的技术,主要分为两大类:
静态代理
指使用AOP框架提供的命令进行编译,从而在编译阶段就可生成AOP代理类,因此也称为编译时增强
。编译时编织类(特殊编译器实现)加载时进行编织(特殊的类加载器实现)。
动态代理
在运行时在内存中“临时”生成AOP动态代理类,因此也被称为运行时增强
。主要有JDK动态代理
和CGLIB动态代理
两种方式
SpringAOP基于动态代理方式实现,AspectJ基于静态代理方式实现
。另外,SpringAOP仅支持方法级别的PointCut,提供了完全的AOP支持,它还支持属性级别的PointCut,功能更加强大
。
将Advice通知
应用于目标对象
后创建的对象称为代理,代理是通知目标对象后创建的对象
,在客户端对象的情况下,目标对象和代理对象是相同的。
AOP底层是使用cglib动态代理来实现,而cglib是创建动态子类继承了业务方法类,在业务方法操作之前,来完成功能的前置或者后置增强
Advice+TargetObject=Proxy
相同点:
都是接口
主要区别: BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean
。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
详细的内容可以参考资料: https://www.cnblogs.com/aspirant/p/9082858.html
从源码级别来看, @Repository、@Service、@Controller是@Component的别名
,@Service用于标注业务层组件
,@Controller用于标注控制层组件
(如struts中的action),@Repository用于标注数据访问组件
,即Dao组件,@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注
。
参看源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
注:
@Repository这个注解是具有类似用途和功能的@Component注解的特化 。@Repository为DAO提供了额外的好处,它将DAO导入IoC容器,并使未经检查的异常有资格转换为Spring DataAccessException
Spring中,我们可以给bean实例声明一个作用域,我们可以通过bean定义中的scope属性来定义
。
比如,当Spring要在需要的时候每次生产一个新的bean实例,bean的scope属性被指定为prototype
。如果一个bean每次使用的时候必须返回同一个实例,这个bean的scope属性必须设为singleton
。
Spring默认的Springbean的scop作用域是Singleton
Spring框架支持以下五种bean的作用域:
答: Spring 框架中的单例bean不是线程安全的
Spring框架并没有对单例bean进行任何多线程的封装处理,关于单例bean的线程安全和并发问题需要开发者自行去搞定。最简单的有效解决办法就是:将多态bean的作用域由“singleton”变更为“prototype”
。
一定要定义变量的话,用ThreadLocal来封装,这个是线程安全的
ThreadLocal threadLocal = new ThreadLocal<>(); // 用ThreadLocal来封装变量
参考资料: Spring框架中的单例bean是线程安全的吗
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。
意图: 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换
主要解决: 在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护
何时使用: 一个系统有许多许多类,而区分它们的只是他们直接的行为
如何解决: 将这些算法封装成一个一个的类,任意地替换
关键代码: 实现同一个接口
应用实例:
1、诸葛亮的锦囊妙计,每一个锦囊就是一个策略
2、旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略
3、JAVA AWT 中的 LayoutManager。
优点: 1、算法可以自由切换。 2、避免使用多重条件判断。 3、扩展性良好。
缺点: 1、策略类会增多。 2、所有策略类都需要对外暴露。
使用场景:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为
2、一个系统需要动态地在几种算法中选择一种
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现
注意事项: 如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题
这个根据实际情况回答即可
示例1: 我在项目中,做日志管理模块时,使用了一个代理模式
。在访问业务方法时,基于AOP切面,对业务进行了拦截。
具体操作大致如下:
首先我自定义了OperateLog
注解,然后设计了日志切面管理类
,使用@Pointcut定义切入点
,在加注解的位置切入代码,使用了@Around环绕通知
,在业务操作前进行Api基本信息的存储,当业务方法执行完成后,存储了相应结果JSON报文,以及Api请求到响应的耗时时间。
示例2: 我在做车辆发行时,多线程执行job任务时,使用了单例模式,封装了一个线程操作工具类,代码示例如下:
package com.smilehappiness.service.common.utils;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.util.concurrent.*;
/**
*
* 线程工具类
*
*
* @author smilehappiness
* @Date 2018/10/27 10:05
*/
public class ThreadUtil {
private static volatile ThreadPoolExecutor THREAD_POOL = null;
private ThreadUtil() {
}
static {
//如果实例为空,创建实例
if (THREAD_POOL == null) {
synchronized (ThreadUtil.class) {
if (THREAD_POOL == null) {
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
ThreadFactory nameThreadFactory = new ThreadFactoryBuilder().setNameFormat("vehicle-pool-%d").build();
THREAD_POOL = new ThreadPoolExecutor(corePoolSize , corePoolSize + 1, 30, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100000), nameThreadFactory);
}
}
}
}
public static void execute(Runnable runnable) {
THREAD_POOL.execute(runnable);
}
/**
*
* 返回结果
*
*
* @param runnable
* @return java.util.concurrent.Future>
* @Date 2018/10/27 11:36
*/
public static Future submit(Runnable runnable, T result) {
return THREAD_POOL.submit(runnable, result);
}
public static Future submit(Callable task) {
return THREAD_POOL.submit(task);
}
/**
*
* 通知关闭线程池
*
*
* @param
* @return void
* @Date 2018/10/27 11:07
*/
public static void shutdown() {
THREAD_POOL.shutdown();
}
public static ThreadPoolExecutor getExecutorObject() {
return THREAD_POOL;
}
}
示例3:
再做医疗供应链保理时,搜集票据
和单据
时,我使用了策略模式,不同的excel数据,我使用不同的模板策略去解析处理
优秀的Spring Boot框架大量使用了这些设计模式
原子性(Atomicity)
事务开始后所有操作,要么全部做完(要么都成功),要么全部不做(要么都失败),不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
一致性(Consistency)
事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
隔离性(Isolation)
同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程中,B不能向这张卡转账。
持久性(Durability)
事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
MySQL默认的隔离级别是可重复读(repeatable-read)
,MySQL事务隔离级别有以下四种:
事务隔离级别
脏读
不可重复读
幻读
读未提交(read-uncommitted)
是
是
是
读已提交(read-committed)
否
是
是
可重复读(repeatable-read)
否
否
是
串行化(serializable)
否
否
否
脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
具体示例,可以参考我另一篇博客: https://blog.csdn.net/smilehappiness/article/details/119656138
服务器硬件
cpu、内存、磁盘io、网卡流量
服务器的操作系统
Linux的配置参数不同性能不同
数据库存储引擎的选择
根据需要选择不同的存储引擎,MyISAM 不支持事务,表级锁,InnoDB 事务级存储引擎,完美支持行级锁和事务ACID特性
数据库自身配置参数
MySQL有上百项的数据库配置参数;(my.cnf)
数据库表结构的设计和SQL语句的执行效率(影响最大的)
慢查询是性能问题的罪魁祸首,不合理的数据库表结构设计和不合理的索引是影响数据库查询性能的重要因素
数据库架构
高并发下读写分离、分库分表、多级缓存、搜索引擎
主键索引
,也叫集聚索引,也叫聚簇索引(针对innodb而言)。辅助索引
,也叫二级索引,也叫非集聚索引,也叫非聚簇索引。
主键索引在叶子节点上可以直接查找到数据,而InnoDB的辅助索引(Secondary Key)(普通索引)data域存储相应记录主键的值而不是当前索引键的值,也不是数据地址,换句话说,InnoDB的所有辅助索引都引用主键作为data域,所以对于辅助索引,还需要主键值来回表查询到一条完整记录
,因此按辅助索引检索实际上进行了二次查询,效率肯定是没有按照主键检索高的。
可以参考我的另一篇博客https://blog.csdn.net/smilehappiness/article/details/119656138,11.2那一节,为什么是B+Tree索引,你就可以理解它的实现原理。
使用EXPLAIN
,在查询sql前添加该关键字,进行执行计划的分析。
执行计划的type属性,是sql查询优化中一个很重要的指标,结果值从好到坏依次是:
NULL > system > const > eq_ref > ref > range > index > ALL
在使用like模式搜索时,可能会导致索引失效
如: like '9999%'
可以用到索引,而like ‘%9999%’
、like '%9999’
用不到索引,比如下面这条sql,就无法命中索引:
EXPLAIN select * from users where name like '%张三';
如果查询条件中有函数或表达式,则MySQL不会为该列使用索引
explain select * from users where id = id - 2;
explain select * from users where left(name, 5) = 'zhangsan';
sql使用or进行查询时,会导致索引失效
下面这两个sql,or导致索引失效
EXPLAIN select * from users where name = 'zhangsan' or phone = '18266668888';
EXPLAIN select * from users where phone in ('18266668888') or name in ('zhangsan');
这种情况指的是,只使用联合索引的第一列进行匹配,如果使用索引的第二列或者其他列,而不是用第一列查询,无法命中索引
,例如:
EXPLAIN select * from users where name = 'zhangsan';
EXPLAIN select * from users where name = 'zhangsan' and date(create_time) = '2021-06-28';
-- 上面两条sql使用了最左列字段,可以命中索引,而下面这一条,只使用了第二列,无法命中索引
EXPLAIN select * from users where phone = '18266668888';
索引下推是针对回表查询进行的优化
,那么,回表有什么坏处呢?
普通索引查询数据时,会进行回表操作,但是回表会增加查询的步骤、降低查询效率
,试想你创建了一个(name,age)的二级索引。当你通过name和age查询时,会先在二级索引上根据name得到主键值并回表,回表后还要在主键索引上查询age。
可以发现回表直接导致了二级索引上的age没有被使用到,当使用索引下推
后就不存在这种情况了。MySQL是默认开启索引下推的。
手动开关代码如下:
set optimizer_switch='index_condition_pushdown=off'
set optimizer_switch='index_condition_pushdown=on'
使用索引下推后会优先在索引上进行查询过滤,使用explain查询会发现:
当Extra
的值为Using index condition
,说明你查询的字段全部在索引上完成了过滤操作,回表时会根据主键直接得到对应的行数据。
当Extra的值为Using where
时,可能你的查询字段没有设置索引,这时会直接在主键索引上查询,效率低下。
当Extra的值为Using index condition; Using where
时,说明你查询的一部分部分字段有索引,另一部分没有索引,当有索引的字段完成查询时回表,并不会立马返回数据,而是还要再全表查询那些没有索引的字段。
详细的介绍,请参考资料: MySQL数据库三范式
Stream Api,函数作为参数传递
Java8有哪些优点:
Optional
类26、Jvm垃圾回收、老年代、新生代、双亲委派模型等,以及JVM参数优化
27、如何阻止gc垃圾回收?
newSingleThreadExecutor
创建一个单线程的线程池
,这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newFixedThreadPool
创建固定大小的线程池
,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
newCachedThreadPool
创建一个可缓存的线程池
,如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newScheduledThreadPool
创建一个大小无限的线程池
,此线程池支持定时以及周期性执行任务的需求。
Executors返回的线程池对象的弊端如下:
Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致OOM
Integer.MAX_VALUE
,可能会创建大量的线程,从而导致OOM
创建线程池的正确方式:
避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池,在创建的同时,给BlockQueue指定容量就可以了,参考示例如下:
//list数据
List
当然,也可以使用下面方式创建线程:
//list数据
List
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) { }
参数说明如下:
参数
说明
corePoolSize
核心线程数量,会一直存在,除非allowCoreThreadTimeOut设置为true
maximumPoolSize
线程池允许的最大线程池数量
keepAliveTime
线程数量超过corePoolSize,空闲线程的最大超时时间
unit
超时时间的单位
workQueue
工作队列,保存未执行的Runnable 任务
threadFactory
创建线程的工厂类
handler
当线程已满,工作队列也满了的时候,会被调用,被用来实现各种拒绝策略
ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本
。这些对象之间的引用关系如下图所示:
前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
Created with Raphal 2.3.0 开始 Redis缓存中是否有数据 返回正常数据 结束 数据库中是否有数据 更新缓存数据 返回空数据 yes no yes no
缓存穿透
是指大量不存在的key请求,由于缓存没有,便开始查询数据库,但数据库也没有查到数据,比如一些恶意攻击、爬虫等造成大量空命中。即:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,会导致数据库压力过大,甚至宕机
。
解决方案:
方案一: 缓存空结果,对数据库查询不存在的数据仍然记录在缓存中缓存一条数据,比如缓存一条空值 unknow,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用不存在的key暴力攻击,这样能有效的减少查询数据库的次数。(无论如何,要保证数据库的可用性
)
方案二: 使用布隆过滤器
缓存击穿
是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力导致数据库不可用的现象。
高并发条件下,对于热点数据(一般地,80%的情况下都是访问某些热点数据,也就是访问某些热点key,其他key访问会比较少),当数据缓存失效的一瞬间,或者刚开始时缓存中还没有对热点数据进行缓存,所有请求都被发送到数据库去查询,导致数据库被压垮。
解决方案:
方案一: 使用全局互斥锁,就是在访问数据库之前都先请求全局锁,获得锁的那个才有资格去访问数据库,其他线程必须等待。由于现在的业务都是分布式的,本地锁没法控制其他服务器也等待,所以要用到全局锁,比如分布式锁。
方案二: 对即将过期的数据主动刷新,比如起一个后台定时任务轮询,主动更新缓存数据,保证缓存不会全部失效。
方案三: 设置热点数据永远不过期
缓存雪崩
是指:比如我们给所有的数据设置了同样的过期时间,然后在某一个历史性时刻,整个缓存的数据全部过期了,然后瞬间所有的请求都落到数据库,数据库被压垮
,或者是缓存发生故障,导致所有的请求都落入到数据库,数据库被压垮
。
简单来说就是,缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至宕机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库,缓存雪崩更加的严重。
缓存雪崩的核心就是你的缓存不能用了
,不能用了包含两种情况:
突然有一个高并发请求:
解决方案:
事前:
事中:
事后: redis持久化,快速恢复缓存数据
主要使用String类型,对热点数据进行缓存
更新中…
Elasticsearch底层使用了倒排索引,通常情况下,都是基于key去寻找value,这个是通过value反向寻找key
更新中…
项目中使用了RabbitMQ消息组件,
无论基于域名还是基于端口,都是结合location path
,在访问时的进行路径匹配,最终映射到root配置指定的路径,从而访问相关资源
nacos注册与发现以及配置中心管理、spring cloud gateway网关、ribbon负载均衡、feign服务http调用、hystrix熔断降级
注:
基于ribbon实现动态负载均衡,基于nginx实现静态负载均衡
@RefreshScope,注解底层是使用cglib动态代理来实现,而cglib是创建动态子类继承来完成功能的增强
如果A服务调用B服务、然后调用C服务,假设C服务故障无法响应,这时候使用故障备用服务器响应一个结果,或者直接给一个系统升级中的结果也行,不能因为C服务挂了,导致整个系统崩盘。
单应用服务A,可以进行业务细粒度的拆分,比如说crm系统,可以拆分为客户服务、订单服务、附件服务等,每个细粒度的应用服务,又可以进行多实例集群,这样就可以提高系统的吞吐量
分库分表(横向拆分:按省份,每个省份一个库,每个库再分多个订单表,比如0_order、1-order等,当然了,也可以对6556进行取余,余数作为库名,比如0_order、1_order,库划分好了,再分多个表,进行读写分离)
项目中使用的是HikariCP连接池。
常用的参数有:
详细介绍可参考资料: HikariCP数据连接池基础介绍
在项目中,我设计了一个日志管理模块,使用ELK进行了日志的搜集和可视化展示。另外,使用SkyWalking做了分布式链路跟踪,当线上出现问题时,我会第一时间查看是哪个服务挂掉了,然后迅速使用ELK查看那个服务的error级别的日志,可以快速查出问题
多次提交,重复数据问题,基于token做幂等校验
具体实现: 我是基于Redis实现的,请求接口前,先获取token接口,然后第一次执行业务方法时,如果此时用户再次点击提交表单,这时候发现token已经存在,不允许再次提交,当业务方法执行完成后,删除相关的token。
线上问题不能及时反馈,添加钉钉预警及时触达
这个根据实际情况发挥吧,笔者这块掌握的一般,也需要好好学习
Linux使用详解,10分钟带你玩转Linux基础操作
Linux使用详解(进阶篇)
Linux crontab 命令
Linux怎样使用top命令查看系统状态
Vue生命周期函数 也叫Vue生命周期钩子,就是Vue实例在某一时间点自动执行的函数。一共有11个生命周期函数,分别是:
生命周期图示如下:
这块可以参考资料链接: https://www.jianshu.com/p/514fe21b9914
写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,希望尽自己的努力,做到更好,大家一起努力进步!
如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!
给自己的梦想添加一双翅膀,让它可以在天空中自由自在的飞翔!