Java后端面经(部分)

Java开发面经

Java面试篇

一、Java容器

1、容器
Java 容器分为 Collection 和 Map 两大类
Java后端面经(部分)_第1张图片
2、 ArrayList与LinkList
是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构;
3、List、Set、Map 之间的区别是什么?
Java后端面经(部分)_第2张图片
4、HashMap底层原理
HashMap的数据结构:HashMap的数据结构为 数组+(链表或红黑树)
数组的特点:查询效率高,插入,删除效率低。
链表的特点:查询效率低,插入删除效率高。
HashMap高效的原因:增删是在链表上完成的,而查询只需扫描部分,则效率高

参考链接:https://baijiahao.baidu.com/s?id=1665667572592680093&wfr=spider&for=pc

二、向上转型、向下转型

向上转型 : 通过子类对象实例化父类对象(大范围),这种属于自动转换,最终得到父类
向下转型 : 通过父类对象实例化子类对象(小范围),这种属于强制转换,最终得到子类
向上转型的意义:多态化。比如猫和狗都是动物的子类,通过向上转型,传参的时候可以直接传动物,自动匹配猫还是狗,但是不这样,只能规定具体的传参是猫/狗。
向下转型的意义:通过父类强制转换为子类,从而来调用子类独有的方法

三、Java的多线程

1、实现多线程的两种放式
一种是继承 Thread 类,另一种就是实现 Runnable 接口
2、Thread和Runnable的区别
Thread是继承类,Runnable是实现接口可以多继承
3、多线中start和run方法的区别
start是真正的开启多线程且start内部也执行了run方法。run方法只会执行主线程
4、启动多线程的方法
继承Thread类重写run方法直接调用start
实现Runnable接口的需要new一个Thread,通过这个Thread执行start方法
spring中通过ThreadPoolTaskExecutor线程池实例的execute方法或者submit方法
5、多线程中调用wait() 和 sleep()方法有什么不同?
Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而 sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

四、==与equals()的区别

1、==
如果作用于基本数据类型的变量,则直接比较其存储的 值是否相等,
如果作用于引用类型的变量,则比较的是所指向的对象的地址是否相等。

2、equals方法,比较的是是否是同一个对象
首先,equals()方法不能作用于基本数据类型的变量,
另外,equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,所以说所有类中的equals()方法都继承自Object类,在没有重写equals()方法的类中,调用equals()方法其实和使用==的效果一样,也是比较的是引用类型的变量所指向的对象的地址,不过,Java提供的类中,有些类都重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值,比如String类。

五、java序列化

①什么是java序列化?
序列化:把Java对象转换为字节序列(字节流)的过程。 反序列化:把字节序列恢复为Java对象的过程。

②为什么要序列化?
序列化是为了解决在对对象流进行读写操作时所引发的问题。
对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

③如何进行序列化?
将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

六、cookie和session

1、两者的区别
1)cookie数据存放在客户的浏览器上,session数据放在服务器上。
2)单个cookie保存的数据不能超过4K,session的大小不固定。
3)cookie存储在本地不安全,session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能

2、session的使用
session就是服务器给客户端的一个编号。当每个用户首次与服务器建立连接时,他就与这个服务器建立了一个Session,同时服务器会自动为其分配一个SessionID,用以标识这个用户的唯一身份。

七、int和Integer的区别

区别:
1、Integer是int的包装类,int则是java的一种基本数据类型
2、Integer变量必须实例化后才能使用,而int变量不需要
3、Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
4、Integer的默认值是null,int的默认值是0

自动装箱与自动拆箱:
装箱就是自动将基本数据类型转换为包装器类型;拆箱就是自动将包装器类型转换为基本数据类型。
Java后端面经(部分)_第3张图片

八、java的反射机制和动态代理

参考地址:https://www.cnblogs.com/fsmly/p/11031395.html#_label3
1、反射的概念
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以,这是一种动态获取类的信息以及动态调用对象方法的能力。

2、什么是代理模式?
代理模式在Java中应用十分广泛,它说的是使用一个代理将对象包装起来然后用该代理对象取代原始对象,任何原始对象的调用都需要通过代理对象,代理对象决定是否以及何时将方法调用转到原始对象上。这种模式可以这样简单理解:你自己想要做某件事情(被代理类),但是觉得自己做非常麻烦或者不方便,所以就叫一个另一个人(代理类)来帮你做这个事情,而你自己只需要告诉要做啥事就好了。

3、静态代理
概念:静态代理其实就是程序运行之前,提前写好被代理的代理类,编译之后直接运行即可起到代理的效果
缺点:因为每一个代理类只能为一个接口服务(因为这个代理类需要实现这个接口,然后去代理接口实现类的方法),这样一来程序中就会产生过多的代理类

4、动态代理
概念:动态代理是指通过代理类来调用它对象的方法,并且是在程序运行使其根据需要创建目标类型的代理对象。它只提供一个代理类,我们只需要在运行时候动态传递给需要他代理的对象就可以完成对不同接口的服务了

5、动态代理
与AOP(深度学习)

九、Java异常

1、异常的分类
Java后端面经(部分)_第4张图片
所有的异常都是从Throwable继承而来的,是所有异常的共同祖先。Throwable有两个子类,Error和Exception

2、Error和Exception
Error:Error是错误,对于所有的编译时期的错误以及系统错误都是通过Error抛出的。这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
Exception:另外一个非常重要的异常子类。它规定的异常是程序本身可以处理的异常。异常和错误的区别是,异常是可以被处理的,而错误是没法处理的

3、异常的处理
①通过try…catch语句块来处理
②直接抛出,通过throws/throw到上层再进行处理

十、java过滤器——Filter

1、过滤器的概念
过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理
通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理
Java后端面经(部分)_第5张图片
2、应用场景
登陆访问权限控制

3、Filter如何拦截
Filter接口中有一个doFilter方法,当我们编写好Filter,并配置对哪个web资源进行拦截后,WEB服务器每次在调用web资源的service方法之前,都会先调用一下filter的doFilter方法。
web服务器在调用doFilter方法时,会传递一个filterChain对象进来,filterChain对象是filter接口中最重要的一个对象,它也提供了一个doFilter方法,开发人员可以根据需求决定是否调用此方法,调用该方法,则web服务器就会调用web资源的service方法,即web资源就会被访问,否则web资源不会被访问。
Filter接口三大方法:
Java后端面经(部分)_第6张图片
4、Filter在springboot中的实现方法
两种实现方法如图
Java后端面经(部分)_第7张图片
5、多个拦截器如何设置顺序
Java后端面经(部分)_第8张图片

十一、Java拦截器

1、概念
java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。

2、应用场景
拦截未登录用户

3、拦截器的原理
动态代理

4、拦截器中的三大方法(Spring框架中的HandlerInterceptorAdapter抽象类实现自定义拦截器)
Java后端面经(部分)_第9张图片
preHandle返回true进行下一个拦截,返回false不执行下一个拦截器

5、springboot拦截未登录的实现
https://blog.csdn.net/weixin_40612082/article/details/110182085
①自定义拦截器Java后端面经(部分)_第10张图片
②编写拦截器配置文件类并继承 WebMvcConfigurer类,并重写其中的方法 addInterceptors并且在主类上加上注解 @Configuration
Java后端面经(部分)_第11张图片

十二、token(令牌)

登陆权限控制:
浏览器第一次访问服务器,根据传过来的唯一标识userId,服务端会通过一些算法,如常用的HMAC-SHA256算法,然后加一个密钥,生成一个token,然后通过BASE64编码一下之后将这个token发送给客户端;客户端将token保存起来,下次请求时,带着token,服务器收到请求后,然后会用相同的算法和密钥去验证token,如果通过,执行业务操作,不通过,返回不通过信息
Java实现token的生成与验证-登录功能:
https://www.cnblogs.com/achengmu/p/12693260.html

token的常用实现方式:JWT / token+redis
两种方式比较:https://www.zhihu.com/question/274566992

十三、Java代理模式

深入学习:https://www.cnblogs.com/jie-y/p/10732347.html
JDK静态代理、JDK动态代理、CGLib动态代理
Java后端面经(部分)_第12张图片
CGLib动态代理实例:
Java后端面经(部分)_第13张图片
JDK动态代理:https://www.cnblogs.com/andydlz/p/11958914.html
JDK静态代理:https://www.cnblogs.com/muscleape/p/9018299.html

Spring/SpringBoot篇

一、什么是MVC/简述MVC

MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)。
1)最上面的一层,是直接面向最终用户的"视图层"(View)。它是提供给用户的操作界面,是程序的外壳。

2)最底下的一层,是核心的"数据层"(Model),也就是程序需要操作的数据或信息。

3)中间的一层,就是"控制层"(Controller),它负责根据用户从"视图层"输入的指令,选取"数据层"中的数据,然后对其进行相应的操作,产生最终结果。

MVC把应用程序分成了上面3个核心模块,这3个模块又可被称为业务层-视图层-控制层。顾名思义,它们三者在应用程序中的主要作用如下:
  业务层:负责实现应用程序的业务逻辑,封装有各种对数据的处理方法。它不关心它会如何被视图层显示或被控制器调用,它只接受数据并处理,然后返回一个结果。
  视图层:负责应用程序对用户的显示,它从用户那里获取输入数据并通过控制层传给业务层处理,然后再通过控制层获取业务层返回的结果并显示给用户。
  控制层:负责控制应用程序的流程,它接收从视图层传过来的数据,然后选择业务层中的某个业务来处理,接收业务层返回的结果并选择视图层中的某个视图来显示结果。
  可以用下图来表示MVC模式中三者之间的关系:
  Java后端面经(部分)_第14张图片
补充:MVC的工作原理
1、 首先用户发送请求到前端控制器,前端控制器根据请求信息(如 URL)选择页面控制器进行处理并把请求委托给它
2、 页面控制器接收到请求后,进行功能处理完毕后返回一个 ModelAndView(模型数据和逻辑视图名);
3、 前端控制器收回控制权,然后根据返回的逻辑视图名,选择相应的视图进行渲染,并把模型数据传入以便视图渲染;
4、 前端控制器再次收回控制权,将响应返回给用户。

二、spring bean的作用域

Java后端面经(部分)_第15张图片
五种作用域中,request、session和global session三种作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。

1、Singleton
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象
2、Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。
3、Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。
4、Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例
5、Global Session
当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例

三、spring bean的线程安全问题

Spring容器生成的Bean都是默认单例的,在单例模式下多线程也可以同时访问同一个对象。如果类中没有非静态成员变量 ,即使有多个线程同时访问单例对象的成员方法也不会出问题。因为每个线程在栈和方法区中会有私有的存储空间,并不会导致数据混乱出现类似事务ACID的问题。线程安全只是针对全局变量!
如何解决呢?
第一种方式:既然是全局变量惹的祸,那就将全局变量都编程局部变量,通过方法参数来传递。
第二种方式: jdk提供了java.lang.ThreadLocal,它为多线程并发提供了新思路。 (当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本)

四、SpringBoot事务

注解方式开启事务:@EnableTransactionManagement和@Transactional
事务不生效的几种原因:

①mysql的MyISAM引擎不支持回滚,如果需要自动回滚事务,需要将mysql的引擎设置成InnoDB;

②在业务中抛出异常时,本应该被事务管理器捕获的异常,被手动catch处理了,或者事务结果未满足具体业务需求的,如果需要手动catch异常做业务处理,需要在catch里手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),或者在catch中主动抛出异常throw new RuntimeException();.

③默认的spring事务只会捕获RuntimeException,如果是非运行时异常也需要进行事务回滚的话,可以在@Transactional注解中加上rollbackfor = Exception.class属性;

④项目中没有配置事务管理器(大坑!我遇到的就是这个),需要在配置类或者配置文件中配置,以本人的配置类为例,因为项目是多数据源的,所以要区别配置不同数据源的事务管理器.
Java后端面经(部分)_第16张图片

五、springboot中的注解

1、@configuration和@component区别
@Configuration是@Component的扩展,在被@Configuration注解的类中所有带有@Bean注解的方法都会被CGLib动态代理,而后每次调用这些方法时返回的都是第一次返回的实例。虽然Component注解也会当做配置类,但是并不会为其生成CGLIB代理Class,所以在生成Driver对象时和生成Car对象时调用car()方法执行了两次new操作,所以是不同的对象。当时Configuration注解时,生成当前对象的子类Class,并对方法拦截,第二次调用car()方法时直接从BeanFactory之中获取对象,所以得到的是同一个对象。
被@Configuration标记的类不能是final类,不能是本地类、访问修饰符也不能是private
2、springboot核心注解
@SpringBootApplication=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan
@EnableAutoConfiguration:将所有符合自动配置条件的bean定义加载到IoC容器
@SpringBootConfiguration:这是一个配置文件类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名。
3、常用注解及其作用
@RestController:主要作用于Controller的类上,它是@Controller和@ResponseBody的组合注解,主要用于返回json数据。
@ResponseBody:主要作用于控制层的类上,主要用于返回json数据。
@RequestMapping:主要作用于Controller类及方法上,主要作用是请求地址的映射
@Controller:主要作用于控制层类上,用于处理http请求等。
@Import:只能用在类上 ,@Import通过快速导入的方式实现把实例加入spring的IOC容器中
@Qualifier:当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用。

六、IoC

IoC控制反转:控制权反转给spring容器,目的是解耦
DI:依赖注入,IoC容器注入某个对象给应用程序

1、为什么要控制反转?
软件系统在没有引入IoC容器之前,对象A依赖对象B,那么A对象在实例化或者运行到某一点的时候,自己必须主动创建对象B或者使用已经创建好的对象B,其中不管是创建还是使用已创建的对象B,控制权都在我们自己手上。
如果软件系统引入了Ioc容器之后,对象A和对象B之间失去了直接联系,所以,当对象A实例化和运行时,如果需要对象B的话,IoC容器会主动创建一个对象B注入到对象A所需要的地方。

2、Ioc的好处
1).可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。
2).每个开发团队的成员都只需要关注自己要实现的业务逻辑,完全不用去关心其他人的工作进展,因为你的任务跟别人没有任何关系,你的任务可以单独测试,你的任务也不用依赖于别人的组件,再也不用扯不清责任了
3).可复用性好

3、依赖注入的三种方式和比较
在Java中依赖注入有以下三种实现方式:
1.构造器注入
2.Setter方法注入
3.接口注入
Java后端面经(部分)_第17张图片

七、AOP面向切片编程

Java后端面经(部分)_第18张图片

应用场景:事务管理、操作日志、安全检查
Java后端面经(部分)_第19张图片
1、底层原理
动态代理:JDK动态代理+CGLB动态代理
JDK动态代理只能实现接口方式代理,所以目标类有接口用JDK代理,没有接口用CGLB

2、CGLib动态代理实现方式
Java后端面经(部分)_第20张图片
Java后端面经(部分)_第21张图片
3、springboot通过AspectJ实现AOP
参考:https://blog.csdn.net/u014338530/article/details/88778158
在这里插入图片描述
Java后端面经(部分)_第22张图片
AspectJ 支持 5 种类型的通知注解
1)@Before: 前置通知:在方法执行之前执行的通知.JoinPoint
2)@After: 后置通知, 在方法执行之后执行 , 即方法返回结果或者抛出异常的时候, 下面的后置通知记录了方法的终止.JoinPoint
3)@AfterRunning: 返回通知, 在方法返回结果之后执行
ps:无论方法是正常返回还是抛出异常, 后置通知都会执行. 如果只想在方法返回的时候记录日志, 应使用返回通知代替后置通知.JoinPoint
4)@AfterThrowing: 异常通知, 在方法抛出异常之后.JoinPoint

5) @Around: 环绕通知, 围绕着方法执行(即方法前后都有执行)
环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.ProceedingJoinPoint为其参数,必须有返回值,可以为null
其中每一个注解,都需要指定一个execution,继改切面作用在哪一个方法之中,样例如下:
@Before(value = “execution(* com.springboot.aspectj.springboot_aspect.service..(…))”)
Java后端面经(部分)_第23张图片
切入点表达式Java后端面经(部分)_第24张图片
4、springboot自定义注解放式实现AOP
①yml开启aop配置(导包略)
Java后端面经(部分)_第25张图片
②自定义注解annotation
Java后端面经(部分)_第26张图片

③aspectJ配置
Java后端面经(部分)_第27张图片
④实现类的方法上加上自定义注解
Java后端面经(部分)_第28张图片
参考:https://www.cnblogs.com/wenjunwei/p/9639909.html

八、Mybatis

1、#{}和${}的区别是什么?
①#{} 对传入的参数视为字符串,dollar{} 对传入的参数视为数字类型
②#会进行预编译,dollar则不会
③dollar有被sql注入的风险,dollar则不会

2、Mybatis中mapper层接口是如何与XML建立SQL关联的?
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,根据类的全限定名+方法名,唯一定位到一个MapperStatement并调用执行器执行所代表的sql,然后将sql执行结果返回。

3、Mybatis是什么?
MyBatis是一款优秀的基于java的持久层框架,它内部封装了jdbc,使开发者只需要关注sql语句本身,而不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。

数据库MySql篇

一、事务的属性

原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。在这里插入图片描述
Java后端面经(部分)_第29张图片

二、简述脏读、不可重复读、幻读

1、脏读(Dirty Read)

所谓脏读是指一个事务中访问到了另外一个事务未提交的数据。如A事务对一条数据进行了修改,但是事务还没提交,此时B事务读到了A事务修改了但是未提交的数据,这就是脏读。

2、不可重复读(Non-repeatable read)

是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。

3、幻读(Phantom Read)

所谓幻读是指同一个事务内多次查询返回的结果集不一样(比如增加了或者减少了行记录)。比如同一个事务A内第一次查询时候有n条记录,但是第二次同等条件下查询却又n+1条记录,这就好像产生了幻觉,为啥两次结果不一样那。其实和不可重复读一样,发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据。不同在于不可重复读是同一个记录的数据内容被修改了,幻读是数据行记录变多了或者少了

三、事务隔离级别

1、Read Uncommitted(读未提交)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读。

2、Read Committed(读已提交)

这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这就是所谓的不可重复读(Non-repeatable read)。

3、Repeatable Read(可重复读)

这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

4、Serializable(串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

四、MySQL B+Tree索引和Hash索引的区别?

由于 hash 索引结构的特殊性,其检索效率非常高,但是 Hash 索引本身由于其特殊性也带来了很多限制和弊端,如果创建 Hash 索引,那么将会存在大量记录指针信息存于同一个 Hash 值相关联。这样要定位某一条记录时就会非常麻烦,会浪费多次表数据的访问,而造成整体性能低下。

五、乐观锁和悲观锁

1、悲观锁
当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制,又称悲观锁。

2、悲观锁的实现
传统的关系型数据库使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
Java 里面的同步 synchronized 关键字的实现。

3、悲观锁的分类
①共享锁【shared locks】又称为读锁,简称S锁。顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
②排他锁【exclusive locks】又称为写锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改。

4、悲观锁的性能
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。

5、乐观锁
乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。

6、乐观锁的实现
①CAS 实现:Java 中java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种 CAS 实现方式。
②版本号控制:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会+1。当线程A要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

7、乐观锁的性能
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。

参考:https://www.jianshu.com/p/d2ac26ca6525

六、关系型数据库和非关系型数据库的区别

非关系型数据库的优势:
1、格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,而关系型数据库则只支持基础类型。
2、速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;
3、高扩展性,基于键值对,数据之间没有耦合性,所以非常容易水平扩展。
4、成本低:nosql数据库部署简单,基本都是开源软件。
5、易于数据的分散,分布式数据库
各个数据之间存在关联是关系型数据库得名的主要原因,为了进行join处理,关系型数据库不得不把数据存储在同一个服务器内,这不利于数据的分散,这也是关系型数据库并不擅长大数据量的写入处理的原因。相反NoSQL数据库原本就不支持Join处理,各个数据都是独立设计的,很容易把数据分散在多个服务器上,故减少了每个服务器上的数据量,即使要处理大量数据的写入,也变得更加容易,数据的读入操作当然也同样容易。

缺点:
1、不提供关系型数据库对事物的处理。NoSQL数据库只应用在特定领域,基本上不进行复杂的处理,但它恰恰弥补了之前所列举的关系型数据库的不足之处。

关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织
关系型数据库的优势:
1、易于维护:都是使用表结构,格式一致;
2、使用方便:SQL语言通用,可用于复杂查询;
3、复杂操作:支持SQL,可以进行Join等复杂查询,可用于一个表以及多个表之间非常复杂的查询。
3. 事务支持使得对于安全性能很高的数据访问要求得以实现。
4. 由于以标准化为前提,数据更新的开销很小(相同的字段基本上都只有一处)
缺点:
1、读写性能比较差,尤其是海量数据的高效率读写;
2、固定的表结构,灵活度稍欠;
3、高并发读写需求,传统关系型数据库来说,硬盘I/O是一个很大的瓶颈。

七、MySql优化

1、mysql中如何查询哪些慢的查询?
慢查询日志:里面会记录那些比较慢的日志,可以使用pt-query-digest工具进行分析
explain语句:可以分析单条语句的查询效率
show profile、show status、show processlist等语句:查询语句执行慢的各种情况以及消息情况或其它

2、如何优化
①加索引
②分库,读写分离
③有重复读用缓存

八、缓存

1、如何解决缓存与数据库不一致的问题?
更新缓存:数据不但写入数据库,还会写入缓存;优点:缓存不会增加一次miss,命中率高
淘汰缓存(先淘汰缓存,再写数据库):数据只会写入数据库,不会写入缓存,只会把数据淘汰掉;优点:简单

九、Redis

1、redis的五大数据类型
a) 字符串类型 String 添加操作:set
b) 哈希对象 hash 添加操作: hset
c). 列表对象 list 添加操作: lpush
d). 集合对象 set 添加操作:sadd
e). 有序集合对象 sorted set 添加操作: zadd

2、redis如何解决key冲突
拉链发、开放地址法

3、Redis的存储机制和持久化方案?
①RDB
redis的默认方法。先将数据存储到内存,当数据累计到某种设定的阈值后会触发一次DUMP 操作,将变化的数据存进RDB文件。一旦 Redis 异常退出,就会丢失最后一次快照以后更改的所有数据。
在这里插入图片描述

②AOF
先将数据存储到内存。开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件.AOF文件不存储数据,储存的是执行的命令。

4、内存淘汰机制
FIFO 淘汰最早数据、LRU 剔除最近最少使用、和 LFU 剔除最近使用频率最低的数据几种策略

5、缓存穿透、缓存雪崩、缓存击穿
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
①:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
②:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。

缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
①:缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
②:如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
③:设置热点数据永远不过期。

缓存击穿
缓存击穿,就是某个热点数据失效时,大量并发的请求同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
如何避免?
①可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到 DB,减小 DB 压力。
②设置热点数据永远不过期。

十、MySQL —— 为什么需要主键?主键为什么最好是单调递增的?

https://blog.csdn.net/itworld123/article/details/103004407
InnoDB引擎使用聚集索引,数据记录本身被存于主索引(一颗B+Tree)的叶子节点上,这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放。因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点)。
       1、如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。
       这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很多开销在维护索引上。
        2、 如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。

你可能感兴趣的:(java)