1. JVM中内存结构模型
JVM的内存结构大概分为:
堆(Heap):线程共享。所有的对象实例以及数组都要在堆上分配。回收器主要管理的对象。
方法区(Method Area):线程共享。存储类信息、常量、静态变量、即时编译器编译后的代码。
方法栈(JVM Stack):线程私有。存储局部变量表、操作栈、动态链接、方法出口,对象指针。
本地方法栈(Native Method Stack):线程私有。为虚拟机使用到的Native 方法服务。如Java使用c或者c++编写的接口服务时,代码在此区运行。
程序计数器(Program Counter Register):线程私有。有些文章也翻译成PC寄存器(PC Register),同一个东西。它可以看作是当前线程所执行的字节码的行号指示器。指向下一条要执行的指令。
2. Java中多线程在哪些项目中用到了
3. 活锁死锁问题分析,什么是活锁,什么是死锁.出现的原因是什么?如何避免?
活锁指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。处于活锁的实体是在不断的改变状态,活锁有可能自行解开。
解决协同活锁的一种方案是调整重试机制。比如引入一些随机性。例如如果检测到冲突,那么就暂停随机的一定时间进行重试。这回大大减少碰撞的可能性。 典型的例子是以太网的CSMA/CD检测机制。
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
死锁的发生必须具备以下四个必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
产生原因:竞争资源引起进程死锁
当系统中供多个进程共享的资源,其数目不足以满足诸进程的需要时,会引起诸进程对资源的竞争而产生死锁。
解决方法:
在系统中已经出现死锁后,应该及时检测到死锁的发生,并采取适当的措施来解除死锁。
死锁预防。
这是一种较简单和直观的事先预防的方法。方法是通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用。但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。
死锁避免。
系统对进程发出的每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源;如果分配后系统可能发生死锁,则不予分配,否则予以分配。这是一种保证系统不进入死锁状态的动态策略。
死锁检测和解除。
先检测:这种方法并不须事先采取任何限制性措施,也不必检查系统是否已经进入不安全区,此方法允许系统在运行过程中发生死锁。但可通过系统所设置的检测机构,及时地检测出死锁的发生,并精确地确定与死锁有关的进程和资源。检测方法包括定时检测、效率低时检测、进程等待时检测等。
然后解除死锁:采取适当措施,从系统中将已发生的死锁清除掉。
这是与检测死锁相配套的一种措施。当检测到系统中已发生死锁时,须将进程从死锁状态中解脱出来。常用的实施方法是撤销或挂起一些进程,以便回收一些资源,再将这些资源分配给已处于阻塞状态的进程,使之转为就绪状态,以继续运行。死锁的检测和解除措施,有可能使系统获得较好的资源利用率和吞吐量,但在实现上难度也最大。
4. 多线程下如何做到数据共享?(可扩展数据共享的使用场景)
java中线程的两种实现方式,一种是Thread类,一种实现runable接口,实现runable接口可以实现数据共享
使用thread类在操作多线程时无法达到资源共享的目的,而使用runable接口实现多线程操作可以实现资源共享
5. 多线程下如何保证线程安全?(可扩展两种锁)
美团技术团队: 不可不说的Java“锁”事 - 美团技术团队
6. HashMap的底层详细介绍?(1.7和1.8前后区别)
HashMap的实现原理:
1.HashMap的结构是数组+链表 或者 数组+红黑树 的形式
2.HashMap底层的Entry[ ]数组,初始容量为16,加载因子是0.75f,扩容按约为2倍扩容
3.当存放数据时,会根据hash(key)%n算法来计算数据的存放位置,n就是数组的长度,其实也就是集合的容量
4.当计算到的位置之前没有存过数据的时候,会直接存放数据
5.当计算的位置,有数据时,会发生hash冲突/hash碰撞
6.解决的办法就是采用链表的结构,在数组中指定位置处以后元素之后插入新的元素
7.也就是说数组中的元素都是最早加入的节点
8.如果链表的长度>8时,链表会转为红黑树,当链表的长度<6时,会重新恢复成链表
在JDK1.6,JDK1.7中,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个链表的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。
7. ArrayList和linkList的区别?(使用场景和特性分析)
相同点: ArrayList和linkList分别实现了List接口,元素都有下标,数据有序,允许存放重复元素
不同点:
ArrayList底层通过数组,数组扩容实现,默认构造方法新建一个空数组,调用add方法添加数据时扩容为10,空间不足时以1.5倍扩容.扩容时需申请新的连续空间,把旧数组复制过去,添加新的数据,回收旧数组.
linkList底层是链表结构,不需要连续的空间,大小不固定
因为ArrayList是基于索引(index)的数据结构,它使用索引在数组中搜索和读取数据是很快的,可以直接返回数组中index位置的元素,因此在随机访问集合元素上有较好的性能。但是要插入、删除数据却是开销很大的,因为这需要移动数组中插入位置之后的的所有元素。
相对于ArrayList,linkedList的随机访问集合元素时性能较差,因为需要在双向列表中找到要index的位置,再返回;但在插入,删除操作是更快的。因为linkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组。
8. ConCurrentHashMap和HashMap的区别,分段锁机制?(理论)
HashMap底层通过数组+链表 或者 数组+红黑树实现,可以存储null键和null值,线程不安全,并发情况下不可用
在JDK1.7中ConcurrentHashMap采用了分段的数组+链表方式实现,线程安全
通过把整个Map分为N个Segment,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问, 可以提供相同的线程安全,但是效率提升N倍,默认提升16倍
jdk7:
数据结构: ReentrantLock+Segment +HashEntry, 一个segment 中包含一个HashEntry 数组, 每个HashEntry 又是一个链表结构
元素杳询: 二次hash, 第一次Hash定位到segment, 第二次Hash 定位到元素所在的链表的头部
锁: segment 分段锁 segment 继承了ReentrantLock, 锁定操作的segment ,其他的segment 不受影响,并发度为segment 个数,可以通过构造函数指定,数组扩容不会影响其他的segment
get 方法无需加锁, volatile保证
jdk8 :
数据结构: synchronized+CAS+Node+红黑树, Node的vaI和next 都用volatile 修饰, 保证可见性
查找, 替换,赋值操作都使用CAS
锁: 锁链表的head 节点, 不影响其他元素的读写, 锁粒度更细, 效率更高, 扩容时, 阻塞所有的读写操作、并发
读操作无锁:
Node 的val和next 使用volatile 修饰, 读写线程对该变量互相可见
数组用volatile 修饰, 保证扩容时被读线程感知
9. NIO和AIO,BIO三者区别?(理论)
BIO (Blocking I/O):同步阻塞I/O模式。请求数据过程,始终与服务器保持连接,一个链接使用一个线程,造成线程阻塞。
NIO (New I/O):同步非阻塞I/O模式。请求数据过程,如果服务器没有数据返回,采用轮询机制,需要进行I/O处理时,才会使用一个线程去处理,避免了BIO模型下大量线程处于阻塞等待状态。
AIO ( Asynchronous I/O):异步非阻塞I/O模式。异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。AIO相对于NIO的区别在于,NIO需要使用者线程不停的轮询IO对象,来确定是否有数据准备好可以读了,而AIO则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了。
10. 介绍下Spring框架?(从框架简介,到框架核心,到框架特性)
Spring是一个为了解决企业应用开发的复杂性而创建的开源框架,
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。
Spring框架两大核心:IoC和DI
IoC(Inversion of Control)就是将对象创建的权力及对象的生命周期的管理过程交由Spring框架来处理,在开发过程中不在需要关注对象的创建和生命周期的管理,而是在需要对象的时候由Spring框架提供,由Spring框架管理对象创建和生命周期的机制称之为控制反转。
DI(Dependency Injection)依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例,注入到应用系统中的各个关联的组件之中。
11. 介绍下Spring框架IOC和AOP的实现原理?(理论)
依赖注入的思想是通过反射机制实现的,在实例化一个类时,它通过反射调用类中set方法将事先保存在HashMap中的类属性注入到类中。 总而言之,在传统的对象创建方式中,通常由调用者来创建被调用者的实例,而在Spring中创建被调用者的工作由Spring来完成,然后注入调用者,即所谓的依赖注入or控制反转。 注入方式有两种:依赖注入和设置注入; IoC的优点:降低了组件之间的耦合,降低了业务对象之间替换的复杂性,使之能够灵活的管理对象。
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
12. Spring框架中beanFactory和FactoryBean有什么区别?(理论)
BeanFactory是接口,提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范
FactoryBean也是接口,为IOC容器中Bean的实现提供了更加灵活的方式,FactoryBean在IOC容器的基础上给Bean的实现加上了一个简单工厂模式和装饰模式, 我们可以在getObject()方法中灵活配置
区别:BeanFactory是个Factory,也就是IOC容器或对象工厂,FactoryBean是个Bean。在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个Bean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似
13. Spring框架中出现的ABA问题(循环依赖)如何解决?(原理,缓存思路)
构造器循环依赖 依赖的对象是通过构造方法传入的,在实例化bean的时候发生。
构造器循环依赖,本质上是无解的死循环。所以Spring也是不支持构造器循环依赖的,当发现存在构造器循环依赖时,会直接抛出BeanCurrentlyInCreationException 异常。
赋值属性循环依赖 依赖的对象是通过setter方法传入的,对象已经实例化,在属性赋值和依赖注入的时候发生。
赋值属性循环依赖,Spring只支持bean在单例模式下的循环依赖,Spring通过对还在创建过程中的单例bean,进行缓存并提前暴露该单例,使得其他实例可以提前引用到该单例bean。
单例模式下的Setter赋值循环依赖:
主要的就是靠提前暴露创建中的单例实例。 过程如下:
创建A,调用构造方法,完成构造,进行属性赋值注入,发现依赖B,去实例化B。
创建B,调用构造方法,完成构造,进行属性赋值注入,发现依赖C,去实例化C。
创建C,调用构造方法,完成构造,进行属性赋值注入,发现依赖A。这个时候就是解决循环依赖的关键了,因为A已经通过构造方法已经构造完成了,也就是说已经将Bean的在堆中分配好了内存,这样即使A再填充属性值也不会更改内存地址了,所以此时可以提前拿出来A的引用,来完成C的实例化。
这样上面创建C过程就会变成了:创建C,调用构造方法,完成构造,进行属性赋值注入,发现依赖A,A已经构造完成,直接引用,完成C的实例化。
C完成实例化后,注入B,B也完成了实例化,然后B注入A,A也完成了实例化。
为了能获取到创建中单例bean,spring提供了三级缓存来将正在创建中的bean提前暴露。
三机缓存Map的作用如下:
一级缓存,singletonObjects 单例缓存,存储已经实例化的单例bean。
二级缓存,earlySingletonObjects 提前暴露的单例缓存,这里存储的bean是刚刚构造完成,但还会通过属性注入bean。
三级缓存,singletonFactories 生产单例的工厂缓存,存储工厂。
在获取单例Bean的时候,会先从一级缓存singletonObjects里获取,如果没有获取到(说明不存在或没有实例化完成),会去第二级缓存earlySingletonObjects中去找,如果还是没有找到的话,就会三级缓存中获取单例工厂singletonFactory,通过从singletonFactory中获取正在创建中的引用,将singletonFactory存储在earlySingletonObjects 二级缓存中,这样就将创建中的单例引用从三级缓存中升级到了二级缓存中,二级缓存earlySingletonObjects,是会提前暴露已完成构造,还可以执行属性注入的单例bean的。 这个时候如何还有其他的bean也是需要属性注入,那么就可以直接从earlySingletonObjects中获取了。
14. Spring框架中Bean对象的生命周期?(原理)
-
- Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- Bean实例化后对将Bean的引入和值注入到Bean的属性中
-
- 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
- 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
-
- 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
- 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
-
- 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
- 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
-
- 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
15. SpringMVC框架的工作原理(带入项目中一个功能阐述,例如登录,查询)
Java应用放入Tomcat中, Tomcat 是一个servlet容器,每一个发送给Tomcat服务器的HTTP请求自然会被一个Java Servlet处理。
Spring应用入口就是DispatcherServlet。
GenericServlet <- HttpServlet <- HttpServletBean <- FreamworkServlet <- DispatcherServlet
父类中正儿八经的servlet类是HttpServletBean这个servlet类是应用启动入口。其生命周期的第一阶段init()方法完成初始化工作。
DispatcherServlet 初始化之后,便可以工作了。当请求到达之时,会调用其doService()方法。
protected void doService(HttpServletRequest request, HttpServletResponse response) {
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
try {
doDispatch(request, response);
}
}
doService()方法先设置一些 request信息。设置完后,它调用doDispatch() 方法,分发请求。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
try {
processedRequest = checkMultipart(request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
doDispatch() 方法会调用getHandler() 方法来找到合适的 handler 来处理请求,会根据请求url去遍历一个HandlerMapping的List,得到的是一个HandlerExecutionChain(执行链)。spring需要从已注册的HandlerMapping接口实现类里边去找。由于我们一般都使用注解形式:@Controller,@RequestMapping注解。因此这里找到HandlerMapping实现就是RequestMappingHandlerMapping
接着会调用getHandlerAdapter()方法找到最终的handler适配器,找到的适配器就是RequestMappingHandlerAdapter,这里得到的适配器HandlerAdapter 所适配HandlerMethod就是用@Controller,@RequestMapping对其分别进行注解的方法。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
确定了HandlerAdapter之后,就要执行handle() 方法,将执行结果ModelAndView返回给DispatcherServlet,DispatcherServlet将ModelAndView传给ViewReslover视图解析器。解析后返回具体View。
DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
DispatcherServlet响应用户。
16. SpringMVC 框架从发起请求到接受请求发生了什么?(网络相关TCP/IP)
域名解析
先解析DNS,根据域名解析获得目标服务器IP,然后根据IP和端口号建通过“三次握手”建立TCP连接
——TCP三次握手
第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
第二次握手:服务器端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务器端将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
第三次握手:客户端收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
——IP层发起http请求
——服务器响应http请求,返回结果
——浏览器解析html,请求具体的资源文件
——浏览器对页面进行渲染
17. MyBatis框架如何处理sql注入(语句)的问题?(#和$的区别)
应用Mybatis框架SQL语句安全写法(即JDBC预编译模式)可以写为:#{参数},这种写法可以很好地避免SQL注入漏洞的产生。
如果在开发过程中没有采用JDBC的预编译模式,如将SQL语句写为:${参数},这种写法就产生了SQL语句的动态拼接。因为”${xxx}”这样格式的参数会直接参与SQL语句的编译,从而不能避免SQL注入攻击。
mybatis是如何做到sql预编译的呢?其实在框架底层,是jdbc中的PreparedStatement类在起作用,PreparedStatement是我们很熟悉的Statement的子类,它的对象包含了编译好的sql语句。这种“准备好”的方式不仅能提高安全性,而且在多次执行一个sql时,能够提高效率,原因是sql已编译好,再次执行时无需再编译。
Mybatis框架下易产生SQL注入漏洞的情况主要分为以下三种:
1、模糊查询
Select* fromnews wheretitle like‘%#{title}%'
使用#程序会报错,把#号改成了$,这样如果java代码层面没有对用户输入的内容做处理势必会产生SQL注入漏洞。
正确写法:
select* fromnews wheretile like concat(‘%',#{title}, ‘%')
2、in 之后的多个参数
in之后多个id查询时使用# 同样会报错,
Select* fromnews whereid in(#{ids})
正确用法为使用foreach,而不是将#替换为$
`
`#{ids}`
``
3、order by 之后
这种场景应当在Java层面做映射,设置一个字段/表名数组,仅允许用户传入索引值。这样保证传入的字段或者表名都在白名单里面。
18. MyBatis框架有几级缓存,默认开启几级(理论性)
为了提升查询效率,提高用户体验,MyBatis提供了数据缓存支持,依据数据缓存的有效范围默认定义了一级缓存和二级缓存
一级缓存(Mybatis默认开启一级缓存)
1.什么是一级缓存
一级缓存是SqlSession级别的缓存,是基于PerpetualCache的HashMap本地缓存。在操作数据库时需要构造sqlSession对象,在对象中有个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的SqlSession之间缓存数据区域 (HashMap)是互不影响的。
2.作用域
一级缓存的作用域是同一个SqlSession在同一个SqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据(前提是同一个session),将不再从数据库查询,从而提高查询效率。
注意:当 Session flush ,commit(执行插入、更新、删除),或 close 之后,该Session中的所有 Cache 将清空。
二级缓存
1.什么是二级缓存
二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等。
2. 作用域
二级缓存是多个SqlSession共享的,其作用域是mapper的同一个namespace,不同的sqlSession多次执行相同namespace下的Sql语句第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
二级缓存是跨SqlSession的
重点:因为mybatis默认一级缓存一直存在(为了数据的安全性考虑),所以说只有等session操作完成,并且close,或commit该session后(建议每次查询都记得关闭会话,commit使用不当的话会将所有缓存清空),查到的值才会存入到二级缓存中,供其他session使用
启用二级缓存
1).在核心配置文件Mybatis.xml中配置
(默认是开启的但是必须配置cache标签才能使用所以可以不配置)
| Mybatis.xml文件配置 | |||
| |
2).在Mapper.xml中开启二级缓存,当该namespace下的 sql执行完成会存储到它的缓存区域
(在mapper下第一行配置)
| mapper.xml 文件配置 | |||
| |
19. SpringBoot框架的项目启动流程?(理论性的内容)
启动结构图:SpringBoot启动结构图 | ProcessOn免费在线作图,在线流程图,在线思维导图 |
SpringBoot启动流程解析 - 简书
面试题:SpringBoot的启动流程
20. SpringBoot如何实现自动装配?(理论可带入场景)
自动配置的根源--spring-boot-autoconfigure-2.2.1.RELEASE.jar / meta-INF / spring.factories
@SpringBootApplication :SpringBoot的主配置类
@EnableAutoConfiguration :开启自动配置功能
@import({AutoConfigurationimportSelector.class}) : 给容器导入组件:自动配置导入选择器
protected List
List
Assert.notEmpty(configurations, "No auto configuration classes found in meta-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
public static List
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map
MultiValueMap
if (result != null) {
return result;
} else {
try {
Enumeration
linkedMultiValueMap result = new linkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry, ?> entry = (Entry)var6.next();
String factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [meta-INF/spring.factories]", var13);
}
}
}
SpringBoot在启动的时候从类路径下的meta-INF/spring.factories中获取EnableAutoConfiguration指定的值
将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
自定义配置原理:
狂神说java笔记
@ConditionalOnClass({DataSource.class, EmbeddedDatabaseType.class})//判断此配置是否生效
@EnableConfigurationProperties({DataSourceProperties.class})//与xxxProperties.class绑定
public class DataSourceAutoConfiguration {}
//与自定义配置文件中前缀spring.datasource绑定 动态修改配置
@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties{}
21. 阐述下security框架的授权以及认证流程(带入项目中角色管理模块实现思路)
22. Redis常见的数据类型有哪些?(带入具体类型使用场景)
Redis支持5种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
string 是 redis 最基本的类型,你可以理解成与 Memcached 一模一样的类型,一个 key 对应一个 value。value其实不仅是String,也可以是数字。string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。string 类型是 Redis 最基本的数据类型,string 类型的值最大能存储 512MB。
Redis hash 是一个键值(key => value)对集合。Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
Redis list 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
常用命令:lpush(添加左边元素),rpush,lpop(移除左边第一个元素),rpop,lrange(获取列表片段,LRANGE key start stop)等。
应用场景:Redis list的应用场景非常多,也是Redis最重要的数据结构之一,比如twitter的关注列表,粉丝列表等都可以用Redis的list结构来实现。
List 就是链表,相信略有数据结构知识的人都应该能理解其结构。使用List结构,我们可以轻松地实现最新消息排行等功能。List的另一个应用就是消息队列,
可以利用List的PUSH操作,将任务存在List中,然后工作线程再用POP操作将任务取出进行执行。Redis还提供了操作List中某一段的api,你可以直接查询,删除List中某一段的元素。
实现方式:Redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。
Redis的list是每个子元素都是String类型的双向链表,可以通过push和pop操作从列表的头部或者尾部添加或者删除元素,这样List即可以作为栈,也可以作为队列。 获取越接近两端的元素速度越快,但通过索引访问时会比较慢。
使用场景:消息队列系统:使用list可以构建队列系统,使用sorted set甚至可以构建有优先级的队列系统。比如:将Redis用作日志收集器,实际上还是一个队列,多个端点将日志信息写入Redis,然后一个worker统一将所有日志写到磁盘。
Redis set是string类型的无序集合。集合是通过hashtable实现的,概念和数学中个的集合基本类似,可以交集,并集,差集等等,set中的元素是没有顺序的。所以添加,删除,查找的复杂度都是O(1)。
sadd 命令:添加一个 string 元素到 key 对应的 set 集合中,成功返回1,如果元素已经在集合中返回 0,如果 key 对应的 set 不存在则返回错误。
常用命令:sadd,spop,smembers,sunion 等。
应用场景:Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
Set 就是一个集合,集合的概念就是一堆不重复值的组合。利用Redis提供的Set数据结构,可以存储一些集合性的数据。
案例:在微博中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。
23. Redis的缓存雪崩和击穿如何解决,穿透问题遇到过吗?(全解释清楚,带入项目场景)
普通解析:【面试】redis缓存穿透、缓存击穿、缓存雪崩区别和解决方案_fcvtb的博客-CSDN博客
:REDIS缓存穿透,缓存击穿,缓存雪崩原因+解决方案 - 大码哥 - 博客园
高端解析:Redis缓存击穿、雪崩、穿透!(超详细)_lin777lin的博客-CSDN博客_redis缓存击穿
缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。
解决方案:
从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒,避免Redis缓存大量空数据
采用布隆过滤器,将所有存在的数据的key哈希到一个足够大的bitmap中,一个不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
解决方案:
使用互斥锁(mutex key)业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果
缓存雪崩:当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如DB)带来很大压力。
解决方案:对缓存数据的key设置随机过期时间,使 key 会分散开,一个一个过期,避免集体过期。
24. 数据库中有2000w条数据,而Redis当中只有20w条数据,如何保证Redis中的数据一定是热点数据?(可考虑设置缓存过期时间)
当redis使用的内存超过了设置的最大内存时,会触发redis的key淘汰机制,在redis 3.0中有6种淘汰策略:
-
- noeviction: 不删除策略。当达到最大内存限制时, 如果需要使用更多内存,则直接返回错误信息。(redis默认淘汰策略)
- allkeys-lru: 在所有key中优先删除最近最少使用(less recently used ,LRU) 的 key。
-
- allkeys-random: 在所有key中随机删除一部分 key。
- volatile-lru: 在设置了超时时间(expire )的key中优先删除最近最少使用(less recently used ,LRU) 的 key。
-
- volatile-random: 在设置了超时时间(expire)的key中随机删除一部分 key。
- volatile-ttl: 在设置了超时时间(expire )的key中优先删除剩余时间(time to live,TTL) 短的key。
方案:
限定 Redis 占用的内存,Redis 会根据自身数据淘汰策略,留下热数据到内存。所以,计算一下 20W 数据大约占用的内存,然后设置一下 Redis 内存限制即可,并将淘汰策略为volatile-lru或者allkeys-lru。
设置Redis最大占用内存:
打开redis配置文件,设置maxmemory参数,maxmemory是bytes字节类型
maxmemory 268435456
设置过期策略:
maxmemory-policy volatile-lru
25. MySql数据库中索引的作用和优缺点(理论内容)
优点:
1.大大加快数据的检索速度;
2.创建唯一性索引,保证数据库表中每一行数据的唯一性;
3.加速表和表之间的连接;
4.在使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间。
缺点:
1.索引需要占物理空间。
2.当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,降低了数据的维护速度。
26. MySql中索引有几种,索引怎么使用?(带入具体的项目场景)
普通索引
最基本的索引类型,没有唯一性之类的限制。普通索引可以通过以下几种方式创建:
创建索引,例如CREATE INDEX <索引的名字> ON tablename (列的列表);
修改表,例如ALTER TABLE tablename ADD INDEX [索引的名字] (列的列表);
创建表的时候指定索引,例如CREATE TABLE tablename ( [...], INDEX [索引的名字] (列的列表) );
唯一索引
唯一索引是不允许其中任何两行具有相同索引值的索引。
当现有数据中存在重复的键值时,大多数数据库不允许将新创建的唯一索引与表一起保存。数据库还可能防止添加将在表中创建重复键值的新数据。例如,如果在 employee 表中职员的姓 (lname) 上创建了唯一索引,则任何两个员工都不能同姓。
对某个列建立UNIQUE索引后,插入新记录时,数据库管理系统会自动检查新纪录在该列上是否取了重复值,在CREATE TABLE 命令中的UNIQE约束将隐式创建UNIQUE索引。
创建唯一索引的几种方式:
创建索引,例如CREATE UNIQUE INDEX <索引的名字> ON tablename (列的列表);
修改表,例如ALTER TABLE tablename ADD UNIQUE [索引的名字] (列的列表); ;
创建表的时候指定索引,例如CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (列的列表) );
主键索引
简称为主索引,数据库表中一列或列组合(字段)的值唯一标识表中的每一行。该列称为表的主键。
在数据库关系图中为表定义主键将自动创建主键索引,主键索引是唯一索引的特定类型。该索引要求主键中的每个值都唯一。当在查询中使用主键索引时,它还允许对数据的快速访问。
提示尽管唯一索引有助于定位信息,但为获得最佳性能结果,建议改用主键索引。
聚集索引
也称为聚簇索引,在聚集索引中,表中行的物理顺序与键值的逻辑(索引)顺序相同。一个表只能包含一个聚集索引, 即如果存在聚集索引,就不能再指定CLUSTERED 关键字。
索引不是聚集索引,则表中行的物理顺序与键值的逻辑顺序不匹配。与非聚集索引相比,聚集索引通常提供更快的数据访问速度。聚集索引更适用于对很少对基表进行增删改操作的情况。
如果在表中创建了主键约束,SQL Server将自动为其产生唯一性约束。在创建主键约束时,指定了CLUSTERED关键字或干脆没有制定该关键字,SQL Sever将会自动为表生成唯一聚集索引。
27. MySql中事务的隔离级别?什么是事务(理论内容)
参考文章:MySQL事务隔离级别和实现原理(看这一篇文章就够了!) - 知乎
什么是事务
数据库事务(Database Transaction),是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。
简单的说:事务就是将一堆的SQL语句(通常是增删改操作)绑定在一起执行,要么都执行成功,要么都执行失败,即都执行成功才算成功,否则就会恢复到这堆SQL执行之前的状态。
事务的特性
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中如果发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事物的隔离级别
读未提交(READ UNCOMMITTED)
MySQL 事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失。而读未提交隔离级别是不加锁的,所以它的性能是最好的,没有加锁、解锁带来的性能开销。但有利就有弊,任何事务对数据的修改都会第一时间暴露给其他事务,即使事务还没有提交。
读未提交,其实就是可以读到其他事务未提交的数据,但没有办法保证你读到的数据最终一定是提交后的数据,如果中间发生回滚,那就会出现脏数据问题,读未提交没办法解决脏数据问题。
读提交 (READ COMMITTED)
读提交就是一个事务只能读到其他事务已经提交过的数据,也就是其他事务调用 commit 命令之后的数据。那脏数据问题迎刃而解了。
读提交事务隔离级别是大多数流行数据库的默认事务隔离界别,比如 Oracle,但是不是 MySQL 的默认隔离界别。
在同一事务中(本例中的事务B),事务的不同时刻同样的查询条件,查询出来的记录内容是不一样的,事务A的提交影响了事务B的查询结果,这就是不可重复读,也就是读提交隔离级别。
读提交解决了脏读的问题,但是无法做到可重复读,也没办法解决幻读。
可重复读 (REPEATABLE READ)
可重复是对比不可重复而言的,上面说不可重复读是指同一事物不同时刻读到的数据值可能不一致。而可重复读是指,事务不会读到其他事务对已有数据的修改,即使其他事务已提交,也就是说,事务开始时读到的已有数据是什么,在事务提交前的任意时刻,这些数据的值都是一样的。但是,对于其他事务新插入的数据是可以读到的,这也就引发了幻读问题。
串行化 (SERIALIZABLE)
串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。



