面试问题目录
- 1. 抽象类和接口的区别
- 2. 了解哪些数据结构
- 3. mysql的数据结构
- 4. spring的工作原理
- 5. java中堆和栈的区别
- 6. mysql和oracle的区别
- 7. spring的三大特性,在项目中哪里遇到过
- 8. java的三大特性
- 9. 数据库的左连接 右连接 内连接
- 11. hibernate 和mybatis的区别
- 12. 简述一下你了解的设计模式
- 13. servlet的运行过程
- 14. java内存模型
- 15. gc清除方式
- 16. Java多线程
- 17. mybatis的一级缓存与二级缓存
- 18. mybatis的sql动态扩展
- 19. @Autowired和@Resource的区别是什么?
- 20. # 和 $ 的区别
- 21. reids
- 22. @Transcational注解原理
- 23. 分布式锁
- 24. TCC 分布式事务
- 25. springCloud
- 26. 数据库索引
- 27. 浅拷贝和深拷贝
- 28. mysql事物
- 29. 了解c3p0,dbcp与druid
- 30. select(*) select(1) select(columnName)
- 31. java 的动态代理和静态代理 使用场景
- 32. Mybatis数据持久化原理
- 33. quartz实现原理
- 34. 自定义注解
- 35. innodb 行锁 表锁 乐观锁 悲观锁
- 36. bean的作用域
- 37. 对象的几种常用方法
- 38. http协议
- 39. JVM-类加载器-双亲委派
- 40. tomcat类加载器
- 41. 常见的垃圾收集器
- 42. Spring的依赖注入有哪些
1. 抽象类和接口的区别
含有abstract修饰符的class即为抽象类,abstract 类不能创建实例对象。含有abstract方法的类必须定义为abstract class,abstract class类中的方法不必是抽象的。abstract class类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。
接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为public abstract类型,接口中的成员变量类型默认为public static final。
下面比较一下两者的语法区别:
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
4.抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然
eclipse下不报错,但应该也不行),但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
5.抽象类中可以包含静态方法,接口中不能包含静态方法
6.抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
7.一个类可以实现多个接口,但只能继承一个抽象类。
总的来说就是如下:
抽象类:1、抽象类使用abstract修饰;2、抽象类不能实例化,即不能使用new关键字来实例化对象;3、含有抽象方法(使用abstract关键字修饰的方法)的类是抽象类,必须使用abstract关键字修饰;4、抽象类可以含有抽象方法,也可以不包含抽象方法,抽象类中可以有具体的方法;5、如果一个子类实现了父类(抽象类)的所有抽象方法,那么该子类可以不必是抽象类,否则就是抽象类;6、抽象类中的抽象方法只有方法体,没有具体实现;
接口:1、接口使用interface修饰;2、接口不能被实例化;3、一个类只能继承一个类,但是可以实现多个接口;4、接口中方法均为抽象方法;5、接口中不能包含实例域或静态方法(静态方法必须实现,接口中方法是抽象方法,不能实现)
2. 了解哪些数据结构
数据结构分类
数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素之间的关系组成 。
常用的数据结构有:数组,栈,链表,队列,树,图,堆,散列表等,如图所示:
1、数组
数组是可以再内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始。例如下面这段代码就是将数组的第一个元素赋值为 1。
优点:
1、按照索引查询元素速度快
2、按照索引遍历数组方便
缺点:
1、数组的大小固定后就无法扩容了
2、数组只能存储一种类型的数据
3、添加,删除的操作慢,因为要移动其他的元素。
适用场景:
频繁查询,对存储空间要求不大,很少增加和删除的情况。
2、栈
栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作。 栈的特点是:先进后出,或者说是后进先出,从栈顶放入元素的操作叫入栈,取出元素叫出栈。
栈的结构就像一个集装箱,越先放进去的东西越晚才能拿出来,所以,栈常应用于实现递归功能方面的场景,例如斐波那契数列。
3、队列
队列与栈一样,也是一种线性表,不同的是,队列可以在一端添加元素,在另一端取出元素,也就是:先进先出。从一端放入元素的操作称为入队,取出元素为出队,示例图如下:
使用场景:因为队列先进先出的特点,在多线程阻塞队列管理中非常适用。
4、链表
链表是物理存储单元上非连续的、非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域 (内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等。
链表的优点:
链表是很常用的一种数据结构,不需要初始化容量,可以任意加减元素;
添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快;
缺点:
因为含有大量的指针域,占用空间较大;
查找元素需要遍历链表来查找,非常耗时。
适用场景:
数据量较小,需要频繁增加,删除操作的场景
5、树
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。把它叫做 “树” 是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。它具有以下的特点:
每个节点有零个或多个子节点;
没有父节点的节点称为根节点;
每一个非根节点有且只有一个父节点;
除了根节点外,每个子节点可以分为多个不相交的子树;
在日常的应用中,我们讨论和用的更多的是树的其中一种结构,就是二叉树。
二叉树是树的特殊一种,具有如下特点:
1、每个结点最多有两颗子树,结点的度最大为2。
2、左子树和右子树是有顺序的,次序不能颠倒。
3、即使某结点只有一个子树,也要区分左右子树。
二叉树是一种比较有用的折中方案,它添加,删除元素都很快,并且在查找方面也有很多的算法优化,所以,二叉树既有链表的好处,也有数组的好处,是两者的优化方案,在处理大批量的动态数据方面非常有用。
扩展:
二叉树有很多扩展的数据结构,包括平衡二叉树、红黑树、B+树等,这些数据结构二叉树的基础上衍生了很多的功能,在实际应用中广泛用到,例如mysql的数据库索引结构用的就是B+树,还有HashMap的底层源码中用到了红黑树。这些二叉树的功能强大,但算法上比较复杂,想学习的话还是需要花时间去深入的。
6、散列表
散列表,也叫哈希表,是根据关键码和值 (key和value) 直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素。
记录的存储位置=f(key)
这里的对应关系 f 成为散列函数,又称为哈希 (hash函数),而散列表就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里,这种存储空间可以充分利用数组的查找优势来查找元素,所以查找的速度很快。
哈希表在应用中也是比较常见的,就如Java中有些集合类就是借鉴了哈希原理构造的,例如HashMap,HashTable等,利用hash表的优势,对于集合的查找元素时非常方便的,然而,因为哈希表是基于数组衍生的数据结构,在添加删除元素方面是比较慢的,所以很多时候需要用到一种数组链表来做,也就是拉链法。拉链法是数组结合链表的一种结构,较早前的hashMap底层的存储就是采用这种结构,直到jdk1.8之后才换成了数组加红黑树的结构,其示例图如下:
从图中可以看出,左边很明显是个数组,数组的每个成员包括一个指针,指向一个链表的头,当然这个链表可能为空,也可能元素很多。我们根据元素的一些特征把元素分配到不同的链表中去,也是根据这些特征,找到正确的链表,再从链表中找出这个元素。
哈希表的应用场景很多,当然也有很多问题要考虑,比如哈希冲突的问题,如果处理的不好会浪费大量的时间,导致应用崩溃。
7、堆
堆是一种比较特殊的数据结构,可以被看做一棵树的数组对象,具有以下的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆的定义如下:n个元素的序列{k1,k2,ki,…,kn}当且仅当满足下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4…n/2),满足前者的表达式的成为小顶堆,满足后者表达式的为大顶堆,这两者的结构图可以用完全二叉树排列出来,示例图如下:
因为堆有序的特点,一般用来做数组中的排序,称为堆排序。
8、图
图是由结点的有穷集合V和边的集合E组成。其中,为了与树形结构加以区别,在图结构中常常将结点称为顶点,边是顶点的有序偶对,若两个顶点之间存在一条边,就表示这两个顶点具有相邻关系。
按照顶点指向的方向可分为无向图和有向图:
图是一种比较复杂的数据结构,在存储数据上有着比较复杂和高效的算法,分别有邻接矩阵 、邻接表、十字链表、邻接多重表、边集数组等存储结构,这里不做展开,读者有兴趣可以自己学习深入。
3. mysql的数据结构
4. spring的工作原理
工作原理就是spring的三大特性,说一说就可以了
5. java中堆和栈的区别
java中堆和栈的区别自然是面试中的常见问题,下面几点就是其具体的区别
各司其职
最主要的区别就是栈内存用来存储局部变量和方法调用。
而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。
独有还是共享
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。
而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
异常错误
如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError。
而如果是堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError。
空间大小
栈的内存要远远小于堆内存,如果你使用递归的话,那么你的栈很快就会充满。如果递归没有及时跳出,很可能发生StackOverFlowError问题。
6. mysql和oracle的区别
首先我觉得这两个数据库就不是一个量级的,mysql轻量级的,oracle重量型的数据库,从安装的步骤就可以感觉得到,所以两个服务有点不同,mysql就没有服务恢复数据,oralce公司就对oracle有很多服务,
再次其是我使用上的感觉,首先就是数据的自增,mysql就有自己的自增字段,但是oracle就需要创建一个序列号,每次新增的时候插入这个序列号的值就可以了
还有就是查询上,比如mysql就直接支持limit来进行分页查询,但是oracle就需要使用一个rownum来查询
还有就是事务上,mysql就是默认提交的,oracle确实要自己手动提交
7. spring的三大特性,在项目中哪里遇到过
1.IOC控制反转
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
2.依赖注入
DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。
理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:
●谁依赖于谁:当然是应用程序依赖于IoC容器;
●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
IoC和DI由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。
类型匹配(Type Matching):虽然我们通过接口(或者抽象类)来进行服务调用,但是服务本身还是实现在某个具体的服务类型中,这就需要某个类型注册机制来解决服务接口和服务类型之间的匹配关系;
构造器注入(Constructor Injection):IoC容器会智能地选择选择和调用适合的构造函数以创建依赖的对象。如果被选择的构造函数具有相应的参数,IoC容器在调用构造函数之前解析注册的依赖关系并自行获得相应参数对象;
属性注入(Property Injection):如果需要使用到被依赖对象的某个属性,在被依赖对象被创建之后,IoC容器会自动初始化该属性;
方法注入(Method Injection):如果被依赖对象需要调用某个方法进行相应的初始化,在该对象创建之后,IoC容器会自动调用该方法
3、AOP:面向切面编程。
AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
https://www.cnblogs.com/jiaweit/p/9986390.html
8. java的三大特性
封装
封装指的是属性私有化,根据需要提供setter和getter方法来访问属性。即隐藏具体属性和实现细节,仅对外开放接口,控制程序中属性的访问级别。
封装目的:增强安全性和简化编程,使用者不必在意具体实现细节,而只是通过外部接口即可访问类的成员。
继承
继承是指将多个相同的属性和方法提取出来,新建一个父类。
Java中一个类只能继承一个父类,且只能继承访问权限非private的属性和方法。 子类可以重写父类中的方法,命名与父类中同名的属性。
继承目的:代码复用。
多态
多态可以分为两种:设计时多态和运行时多态。
设计时多态:即重载,是指Java允许方法名相同而参数不同(返回值可以相同也可以不相同)。
运行时多态:即重写,是指Java运行根据调用该方法的类型决定调用哪个方法。
多态目的:增加代码的灵活度。
9. 数据库的左连接 右连接 内连接
关于左连接和右连接总结性的一句话:
左连接where只影向右表,右连接where只影响左表。
Left Join
select * from tbl1 Left Join tbl2 where tbl1.ID = tbl2.ID
左连接后的检索结果是显示tbl1的所有数据和tbl2中满足where 条件的数据。
简言之 Left Join影响到的是右边的表
Right Join
select * from tbl1 Right Join tbl2 where tbl1.ID = tbl2.ID
检索结果是tbl2的所有数据和tbl1中满足where 条件的数据。
简言之 Right Join影响到的是左边的表。
全连接就是取tb1和tb2都有的数据
11. hibernate 和mybatis的区别
一、两者最大的区别
针对简单逻辑,Hibernate与MyBatis都有相应的代码生成工具,可以生成简单基本的DAO层方法。
针对高级查询,MyBatis需要手动编写SQL语句,以及ResultMap,而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专注于流程。
二、开发难度对比
Hibernate的开发难度大于MyBatis,主要由于Hibernate比较复杂,庞大,学习周期比较长。
MyBatis则相对简单,并且MyBatis主要依赖于生气了的书写,让开发者刚进更熟悉。
三、sql书写比较
Hibernate也可以自己写sql来指定需要查询的字段,但这样就破坏了Hibernate开发的简洁性,不过Hibernate具有自己的日志统计。
MyBatis的sql是手动编写的,所以可以按照要求指定查询的字段,不过没有自己的日志统计,所以要借助Log4j来记录日志。
四、数据库扩展性计较
Hibernate与数据库具体的关联在XML中,所以HQL对具体是用什么数据库并不是很关心
MyBatis由于所有sql都是依赖数据库书写的,所以扩展性、迁移性比较差。
12. 简述一下你了解的设计模式
我们也没必要去记住23种所有的设计模式,只用知道spring当中用了哪些设计模式就可以了,下面就是我的总结
工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
代理设计模式 : Spring AOP 功能的实现。
单例设计模式 : Spring 中的 Bean 默认都是单例的。
模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
单例模式(Singleton),也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为,
单例模式分为: 饿汉模式和懒汉模式
饿汉模式是类加载的时候就创建了对象
懒汉模式需要调用类的时候才会创建对象
推荐饿汉模式
适配器模式,把一个类的接口变换成客户端所期待的另一种接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。
类的适配器模式是把适配的类的API转换成为目标类的API。
观察者模式,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
13. servlet的运行过程
Sevlet程序是由Web服务器调用,其Servlet运行过程简述如下:
(1)Web服务器接收浏览器发出的“http请求”;
(2)Web服务器访问Servlet,如果是首次访问Servlet就必须先创建目标Servlet;
(3)调用Servlet实例对象的init()方法;
(4)创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象;
(5)调用Servlet的service()方法并将请求和响应对象作为参数传递进去。注意,Servlet容器调用service()方法对请求处理;
(6)返回Service方法,服务器卸载一个Servlet实例对象,并且在卸载之前调用Servlet的destroy()方法;
(7)Web服务器向浏览器发出http响应;
14. java内存模型
java内存模型只要就是指的内存区域
程序计数器 ,Java虚拟机栈,本地方法栈,Java堆,方法区
1 程序计数器
程序计数器是一块较小的内存空间,可把它看作当前线程正在执行的字节码的行号指示器。也就是说,程序计数器里面记录的是当前线程正在执行的那一条字节码指令的地址。
A. 程序计数器的作用
字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了
B. 程序计数器的特点
- 占用存储空间很小。
- 线程私有。每个线程都有属于自己的一个程序计数器。
- 是唯一一个不会出现OutOfMemoryError的内存区域。
- 生命周期随着线程的创建而创建,随着线程的结束而死亡。
2 Java虚拟机栈
Java虚拟机栈是描述Java方法运行过程的内存模型。Java虚拟机栈会为每一个即将运行的Java方法创建一块叫做“栈帧”的区域,这块区域用于存储该方法在运行过程中所需要的一些信息,这些信息包括:
- 局部变量表。存放基本数据类型变量、引用类型的变量、返回类型的变量。
- 操作数栈。
- 动态链接。
- 方法出口信息。
3 本地方法栈
本地方法栈和Java虚拟机栈实现的功能类似(注:HotSpot虚拟机中,直接就把本地方法栈和虚拟机栈合二为一),只不过本地方法区是本地方法运行的内存模型。本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间。也会抛出StackOverFlowError和OutOfMemoryError异常。
4 Java堆
堆是用来存放对象的内存空间。 所有的对象实例以及数组都要在堆上进行分配。
5 方法区
方法区中存放已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然JVM规范把方法区描述为一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的是为了和Java堆区分开来。
15. gc清除方式
标记清除:
标记清除的方式需要对程序的对象进行两次扫描,第一次从根(root)开始扫描,被根引用了的对象标记为不是垃圾,不是垃圾的对象引用的对象同样标记为不是垃圾,以此递归。所有不是垃圾的对象的引用都扫描完了之后。就进行第二次扫描,第一次扫描中没有得到标记的对象就是垃圾了,对此进行回收
复制收集
复制收集的方式只需要对对象进行一次扫描。准备一个「新的空间」,从根开始,对对象进行扫,如果存在对这个对象的引用,就把它复制到「新空间中」。一次扫描结束之后,所有存在于「新空间」的对象就是所有的非垃圾对象。
引用计数法
在对象头处维护一个counter,每增加一次对该对象的引用计数器自加,如果对该对象的引用失联,则计数器自减。当counter为0时,表明该对象已经被废弃,不处于存活状态。这种方式一方面无法区分软、虛、弱、强引用类别。另一方面,会造成死锁,假设两个对象相互引用始终无法释放counter,永远不能GC。
三色标记法
这个算法就是把 GC 中的对象划分成三种情况:
白色:还没有搜索过的对象(白色对象会被当成垃圾对象)
灰色:正在搜索的对象
黑色:搜索完成的对象(不会当成垃圾对象,不会被GC)
16. Java多线程
多线程的实现方式大致有以下几种
方式一:继承Thread类的方式
- 创建一个继承于Thread类的子类
- 重写Thread类中的run():将此线程要执行的操作声明在run()
- 创建Thread的子类的对象
- 调用此对象的start():①启动线程 ②调用当前线程的run()方法
方式二:实现Runnable接口的方式
- 创建一个实现Runnable接口的类
- 实现Runnable接口中的抽象方法:run():将创建的线程要执行的操作声明在此方法中
- 创建Runnable接口实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 调用Thread类中的start():① 启动线程 ② 调用线程的run() —>调用Runnable接口实现类的run()
方式三:实现Callable接口
说明:
与使用Runnable相比, Callable功能更强大些
实现的call()方法相比run()方法,可以返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutureTask是Futrue接口的唯一的实现类
FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
方式四:使用线程池
线程池有两个主要作用:
1、不同请求之间重复利用线程,无需频繁的创建和销毁线程,降低系统开销;
2、控制线程数量上限,避免创建过多的线程耗尽进程内存空间,同时减少线程上下文切换次数。
核心参数
1、corePoolSize:核心线程数
- 核心线程会一直存活,及时没有任务需要执行
- 当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理
2、queueCapacity:任务队列容量(阻塞队列)
- 当核心线程数达到最大时,新任务会放在队列中排队等待执行
3、maxPoolSize:最大线程数
- 当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
- 当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常
4、 keepAliveTime:线程空闲时间
- 当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
- 如果allowCoreThreadTimeout=true,则会直到线程数量=0
5、allowCoreThreadTimeout:允许核心线程超时
16.1 线程池的原理
线程池的饱和策略事件,主要有四种类型
AbortPolicy(抛出一个异常,默认的)
DiscardPolicy(新提交的任务直接被抛弃)
DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
CallerRunsPolicy(交给线程池调用所在的线程进行处理,即将某些任务回退到调用者)
16.2 说说TherdLocal
从名字我们就可以看到ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
- 每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。
- 当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
- 当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。
- 总结:当我们调用get方法的时候,其实每个当前线程中都有一个ThreadLocal。每次获取或者设置都是对该ThreadLocal进行的操作,是与其他线程分开的。
16.3 多线程的几种状态
(1) 创建状态
在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread 类的构造方法来实现,例如 “Thread thread=new Thread()”。
(2) 就绪状态
新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。
(3) 运行状态
当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run() 方法定义该线程的操作和功能。
(4) 阻塞状态
一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
(5) 死亡状态
线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
17. mybatis的一级缓存与二级缓存
一级缓存
Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个SqlSession而言,所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用一个Mapper方法,往往只执行一次SQL
1、一级缓存的生命周期有多长?
a、MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用
二级缓存
MyBatis的二级缓存是Application级别的缓存,它可以提高对数据库查询的效率,以提高应用的性能。
SqlSessionFactory层面上的二级缓存默认是不开启的,二级缓存的开席需要进行配置,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。 也就是要求实现Serializable接口,配置方法很简单,只需要在映射XML文件配置就可以开启缓存了,如果我们配置了二级缓存就意味着:
映射语句文件中的所有select语句将会被缓存。
映射语句文件中的所欲insert、update和delete语句会刷新缓存。
缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。
但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。
举个简单的例子:
订单和订单详情,orderMapper、orderDetailMapper。在查询订单详情时我们需要把订单信息也查询出来,那么这个订单详情的信息被二级缓存在orderDetailMapper的namespace中,这个时候有人要修改订单的基本信息,那就是在orderMapper的namespace下修改,他是不会影响到orderDetailMapper的缓存的,那么你再次查找订单详情时,拿到的是缓存的数据,这个数据其实已经是过时的。
根据以上,想要使用二级缓存时需要想好两个问题:
1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。
18. mybatis的sql动态扩展
动态sql的执行原理:
其实就是 select update delete 标签
第一部分:在启动加载解析xml配置文件的时候进行解析,根据关键标签封装成对应的handler处理对象,封装成sqlSource对象存在mappedStatement。
调用流程:
I、SqlSessionFactoryBuilder对builder对象的时候,调用XMLConfigBuilder解析sqlMapConfig.xml配置文件,在解析过程中使用到了私有的mapperElement(XNode parent)方法
II、上面方法中通过构建XMLMapperBuilder,获取到所有的配置mapper配置,
在调用private void configurationElement(XNode context)方法进行解析mapper.xml,通过void buildStatementFromContext(List list, String requiredDatabaseId)方法解析mapper.xml内的每一个标签
III、循环中构建XMLStatementBuilder对象,调用parseStatementNode()方法来封装mappedStatment对象,
IIII、在过程中需要构建sqlSource对象,通过XMLLanguageDriver对象进行处理,在XMLLanguageDriver中构建解析动态标签对象XMLScriptBuilder
第二部分:在执行过程中获取sqlSource中获取bondSql对象时,执行相应的标签handler
调用查询执行到BaseExecutor的query方法时候会去getBoundSql并且将参数传进去,
在sqlSource接口DynamicSqlSource实现类中,调用getBoundSql方法执行过程共创建DynamicContext对象进行判定解析封装成SqlSource对象返回。
19. @Autowired和@Resource的区别是什么?
20. # 和 $ 的区别
#{}和${}这两个语法是为了动态传递参数而存在的,是Mybatis实现动态SQL的基础,总体上他们的作用是一致的
#{} : 采用预编译方式,可以防止SQL注入
${}: 采用直接赋值方式,无法阻止SQL注入攻击
21. reids
1.redis的5种数据类型:
string 字符串(可以为整形、浮点型和字符串,统称为元素)
list 列表(实现队列,元素不唯一,先入先出原则)
set 集合(各不相同的元素)
hash hash散列值(hash的key必须是唯一的)
sort set 有序集合
21.1 redis淘汰策略
我们可以对 8 种淘汰策略可以分为两大类:
不进行淘汰的策略
noevition,redis默认的策略,此策略不会对缓存的数据进行淘汰,当内存不够了就会报错,因此,如果真实数据集大小大于缓存容量,就不要使用此策略了。
会进行淘汰的策略
(1)在所有数据中筛选
- allkeys-random:随机删除
- allkeys-lru:使用 LRU 算法进行筛选删除
- allkeys-lfu:使用 LFU 算法进行筛选删除
(2)在设置了过期时间的数据中筛选
- volatile-random:随机删除
- volatile-ttl:根据过期时间先后进行删除,越早过期的越先被删除
- volatile-lru:使用 LRU 算法进行筛选删除
- volatile-lfu:使用 LFU 算法进行筛选删除
LFU 全称 Least Frequently Used,即最不经常使用策略,它是基于数据访问次数来淘汰数据的
LRU 全称是 Least Recently Used,即最近最少使用,会将最不常用的数据筛选出来,保留最近频繁使用的数据
21.2 集群的实现方式
21.3 redis雪崩,穿透,击穿
雪崩:一瞬间有大量的访问只是数据库崩了,比如12点刷新缓存,在刷新缓存的同时有大量的请求访问,由于缓存中没有数据,知己查询数据库知识服务宕机;
解决:往redis中存入缓存的时候每个key设置失效时间的时候加一个随机数,使其不再同一时间大批量的刷新缓存;如在集群中部署热点数据均匀分布,或者设置热点数据永不过期,有更新的话直接刷新缓存数据即可;
穿透:缓存和数据库中都没有数据,用户一直大批量的请求;比如id=1并且自增长;用户请求的id=-1,并且大批量的请求致使服务宕机;
解决:在请求的接口中添加校验,不合法的请求直接return;
击穿:雪崩是由于缓存中没有数据,大批量的请求导致的;击穿则是缓存中某一个key非常的热点,在不停的扛着大量的请求,大并发集中在这一点进行访问,当这个key失效的瞬间,持续大并发直接落到数据库上致使数据库宕机;
解决:设置热点数据永不过期或者加上互斥锁;
22. @Transcational注解原理
运行配置@Transactional注解的测试类的时候,具体会发生如下步骤
- 事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令(不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚)
- 事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象
22.1 事务失效比较常见的三种情况
自身调用:
注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。
异常被吃
异常被吃的意思是在方法里面如果整体try catch了,但是catch里面没有进行异常抛出,事务要生效必须抛出异常RuntimeException
异常抛出类型不对
在catch里面抛出异常为 throw new Exception(“更新错误”);
这样事务也是不生效的,因为默认回滚的是:RuntimeException
23. 分布式锁
我们在开发应用的时候,如果需要对某一个共享变量进行多线程同步访问的时候,可以使用我们学到的锁进行处理,并且可以完美的运行,毫无Bug!
后来业务发展,需要做集群,一个应用需要部署到几台机器上然后做负载均衡
分布式锁简单介绍两种实现方式
基于数据库实现
基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁
基于redis实现
大致核心思想是差不多的
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
24. TCC 分布式事务
在分布式系统中,因为一个操作可能请求操作多个服务,也会操作多个数据库,这里就需要保证事务 的原子性了,要么都成功,要么都不成功
这里就需要用到TCC分布式事务操作了
T- try:首先尝试预留业务资源
C-Confirm:确认执行业务操作
C-Cancel:取消执行业务操作
这里需要用到框架去感知Try操作, 让框架去做接下来的操作
25. springCloud
简单介绍一下个Spring Cloud核心组件,在微服务架构中,分别扮演的角色:
Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里
Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台
Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题
Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务
26. 数据库索引
说到索引,我们平常最经常用的就是给表加一个主键,也就是唯一索引,但有时候有些人创建表的时候也不会加主键,觉得主键的作用就只有数据唯一的效果
事实上, 一个加了主键的表,并不能被称之为「表」。一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐, 跟我认知中的「表」很接近。如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,也就是上面说的「平衡树」结构,换句话说,就是整个表就变成了一个索引
主键的作用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置
假如1000条数据,没加主键,现在select * from table where id = 1000
那么极有可能找1000次
但是如果加了主键, 数据格式就变成了一个平衡数,只用找几次就找到了
然而, 事物都是有两面的, 索引能让数据库查询数据的速度上升, 而使写入数据的速度下降,原因很简单的, 因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时, DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,也就是为什么索引会给查询以外的操作带来副作用的原因。
26.1 数据库索引优化
- 维度高的列创建索引。
- 数据列中不重复值出现的个数,这个数量越高,维度就越高。
- 如数据表中存在8行数据a,b ,c,d,a,b,c,d这个表的维度为4。
- 要为维度高的列创建索引,如性别和年龄,那年龄的维度就高于性别。
- 性别这样的列不适合创建索引,因为维度过低。
- 对 where,on,group by,order by 中出现的列使用索引。
- 对较小的数据列使用索引,这样会使索引文件更小,同时内存中也可以装载更多的索引键。
- 为较长的字符串使用前缀索引。
- 不要过多创建索引,除了增加额外的磁盘空间外,对于DML操作的速度影响很大,因为其每增删改一次就得从新建立索引
- 使用组合索引,可以减少文件索引大小,在使用时速度要优于多个单列索引。
26.2 复合索引
复合索引的结构与电话簿类似,人名由姓和名构成,电话簿首先按姓氏对进行排序,然后按名字对有相同姓氏的人进行排序。如果您知道姓,电话簿将非常有用;如果您知道姓和名,电话簿则更为有用,但如果您只知道名不姓,电话簿将没有用处。
索引的最左原则(左前缀原则),如(c1,c2,c3,c4…cN)的联合索引,where 条件按照索引建立的字段顺序来使用(不代表and条件必须按照顺序来写),如果中间某列没有条件,或使用like会导致后面的列不能使用索引。
索引也能用于分组和排序,分组要先排序,在计算平均值等等。所以在分组和排序中,如果字段顺序可以按照索引的字段顺序,即可利用索引的有序特性。
27. 浅拷贝和深拷贝
深浅拷贝只是针对Array与Object这样的引用数据类型。简单来说,浅拷贝只是拷贝了它在栈中存储的指针,它们指向的都是同一个堆内存地址,所以浅拷贝在某些情况会造成改变数据后导致别的另一份数据也同步被改变的情况;而深拷贝是直接将堆内存中存储的数据直接复制一份,不会有浅拷贝互相影响的问题。
实现对象拷贝的类,需要实现 Cloneable 接口,并覆写 clone() 方法。
拷贝相比于浅拷贝速度较慢并且花销较大
28. mysql事物
一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
28.1 四种隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
29. 了解c3p0,dbcp与druid
DBCP
DBCP是一个依赖Jakarta
commons-pool对象池机制的数据库连接池.DBCP可以直接的在应用程序中使用,Tomcat的数据源使用的就是DBCP。
c3p0
c3p0是一个开放源代码的JDBC连接池,它在lib目录中与Hibernate一起发布,包括了实现jdbc3和jdbc2扩展规范说明的Connection
和Statement 池的DataSources 对象。
Druid
阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个
ProxyDriver,一系列内置的JDBC组件库,一个 SQL Parser。支持所有JDBC兼容的数据库,包括
Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。
Druid针对Oracle和MySql做了特别优化,比如Oracle的PS Cache内存占用优化,MySql的ping检
测优化。
Druid提供了MySql、Oracle、Postgresql、SQL-92的 SQL的完整支持,这是一个手写的高性
能SQL Parser,支持Visitor模式,使得分析SQL的抽象语法树很方便。简单SQL语句用时10微秒以内,复
杂SQL 用时30微秒。通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的 WallFilter就是通过Druid的SQL Parser分析语义实现的。
30. select(*) select(1) select(columnName)
1. count(1) and count(*)
当表的数据量大些时,对表作分析之后,使用count(1)还要比使用count()用时多了!
从执行计划来看,count(1)和count()的效果是一样的。 但是在表做过分析之后,count(1)会比count(*)的用时少些(1w以内数据量),不过差不了多少。
如果count(1)是聚索引,id,那肯定是count(1)快。但是差的很小的。
因为count(),自动会优化指定到那一个字段。所以没必要去count(1),用count(),sql会帮你完成优化的 因此:count(1)和count(*)基本没有差别!
2. count(1) and count(字段)
两者的主要区别是
(1) count(1) 会统计表中的所有的记录数,包含字段为null 的记录。
(2) count(字段) 会统计该字段在表中出现的次数,忽略字段为null 的情况。即不统计字段为null 的记录。
转自:http://www.cnblogs.com/Dhouse/p/6734837.html
count(*) 和 count(1)和count(列名)区别
执行效果上:
count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL
count(1)包括了忽略所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL
count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。
31. java 的动态代理和静态代理 使用场景
静态代理
Java中的静态代理要求代理类(ProxySubject)和委托类(RealSubject)都实现同一个接口(Subject)。静态代理中代理类在编译期就已经确定,而动态代理则是JVM运行时动态生成,静态代理的效率相对动态代理来说相对高一些,但是静态代理代码冗余大,一单需要修改接口,代理类和委托类都需要修改。
动态代理
Java中的动态代理依靠反射来实现,代理类和委托类不需要实现同一个接口。委托类需要实现接口,否则无法创建动态代理。代理类在JVM运行时动态生成,而不是编译期就能确定。
Java动态代理主要涉及到两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。代理类需要实现InvocationHandler接口或者创建匿名内部类,而Proxy用于创建动态动态。
之间的区别
静态代理实现较简单,代理类在编译期生成,效率高。缺点是会生成大量的代理类。
JDK动态代理不要求代理类和委托类实现同一个接口,但是委托类需要实现接口,代理类需要实现InvocationHandler接口。
动态代理要求代理类InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
常用的使用场景:
数据库的事务:service层的@Transactional注解
全局日志,数据源切换,spring全局异常等场景可以使用spring注解实现
@aspect 切面声明注解
@Pointcut 切入点声明, 表面哪些类要用到这里
32. Mybatis数据持久化原理
1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。
8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
33. quartz实现原理
大致的原理过程:
1、scheduler是一个计划调度器容器(总部),容器里面可以盛放众多的JobDetail和trigger,当容器启动后,里面的每个JobDetail都会根据trigger按部就班自动去执行。
2、JobDetail是一个可执行的工作,它本身可能是有状态的。
3、Trigger代表一个调度参数的配置,什么时候去调。
4、当JobDetail和Trigger在scheduler容器上注册后,形成了装配好的作业(JobDetail和Trigger所组成的一对儿),就可以伴随容器启动而调度执行了。
5、scheduler是个容器,容器中有一个线程池,用来并行调度执行每个作业,这样可以提高容器效率。
34. 自定义注解
注解其实就是一种标记,可以在程序代码中的关键节点(类、方法、变量、参数、包)上打上这些标记,然后程序在编译时或运行时可以检测到这些标记从而执行一些特殊操作
使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。
元注解
- @Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的
- @Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
- Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
- @Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。
35. innodb 行锁 表锁 乐观锁 悲观锁
行锁
顾名思义,行锁就是一锁锁一行或者多行记录,mysql的行锁是基于索引加载的,所以行锁是要加在索引响应的行上,即命中索引
update操作的时候会自动加锁
表锁
顾名思义,表锁就是一锁锁一整张表,在表被锁定期间,其他事务不能对该表进行操作,必须等当前表的锁被释放后才能进行操作。表锁响应的是非索引字段,即全表扫描
insert操作的时候会自动加锁
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。
因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。
因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
36. bean的作用域
Singleton
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例
Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例
Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成
Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效
Global Session
当一个bean的作用域为Global Session,表示在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。
37. 对象的几种常用方法
- getClass
- hashCode
- equals
- clone
- toString
- notify
- notifyAll
38. http协议
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写
HTTP是一个基于TCP/IP通信协议来传递数据
数据格式为:
请求行(request line)、请求头部(header)、空行和请求数据四个部分组成。
39. JVM-类加载器-双亲委派
加载器从上往下依次为:
1. BootstrapClassLoader: 启动类加载器,主要是加载<java_home>/lib下面的东西
2. ExtensionClassLoader:扩展类加载器,主要是加载<java_home>/lib/ext下面的东西
3. ApplicationClassLoader:应用类加载器,主要是加载<class_path>下面的东西
4. 自定义加载器:这个就是加载自己写的一些
双亲委派:
首先从 4 开始问加载没有,如果没有加载,就依次往 3 2 1来问
如果都没有加载,那么从1 开始尝试自己加载,如果不能,就依次从2 3 4 尝试
40. tomcat类加载器
41. 常见的垃圾收集器
1.Serial GC,它是最古老的垃圾收集器,“Serial”体现在其收集工作是单线程的,并且在进行垃圾收集过程中,会进入臭名昭著的“Stop-The-World”状态。当然,其单线程设计也意味着精简的 GC 实现,无需维护复杂的数据结构,初始化也简单,所以一直是 Client 模式下 JVM 的默认选项。
从年代的角度,通常将其老年代实现单独称作 Serial Old ,它采用了标记 - 整理( Mark-Compact )算法
2.ParNew GC,很明显是个新生代GC实现,它实际是Serial GC的多线程版本,最常见的应用场景是配合老年代的CMS GC工作,下面是对应参数
3.CMS(Concurrent Mark Sweep) GC,基于标记-清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间,这一点对于Web等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用 CMS GC 。但是, CMS 采用的标记 - 清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生 full GC ,导致恶劣的停顿。另外,既然强调了并发( Concurrent ), CMS 会占用更多 CPU 资源,并和用户线程争抢。
4.Parrallel GC,在早期JDK 8等版本中,它是server模式JVM的默认GC选择,也被称作是吞吐量优先的GC。它的算法和Serial GC比较相似,尽管实现要复杂的多,其特点是新生代和老年代 GC 都是并行进行的,在常见的服务器环境中更加高效。
42. Spring的依赖注入有哪些
1. 字段注入
字段注入是最常见得注入方式,实现方式非常简单而直接,代码可读性也很强
@Autowired
private TestClass testClass;
但是有三大问题
(1)对象的外部可见性
无法脱离容器而单独使用,不能直接实例化对象,类和容器的耦合度过高
(2)可能导致潜在的循环依赖
两个类之间互相注入
(3) 无法设置需要注入的对象为final,也无法注入那些不可变对象
因为字段必须在类实例化时进行实例化
2. 构造器注入
构造器注入能够保证注入的组件不可变,并且确保需要的依赖不为空
该注入方式可以解决字段注入的三大问题
@Autowired
public ClassTestClinet(ClassTest classTest){
this.classTest = classTest;
}
构造器注入的问题
当构造函数中存在较多依赖对象的时候,大量的构造器函数会让代码显得比较冗长,导致代码的可读性和维护性都大大降低
3. setter方法注入
比构造器函数更具可读性,因为可以把多个依赖对象分别通过Setter方法逐一进行注入,而且Setter方法注入对于非强制依赖注入非常有用,可以按需注入,有选择的注入一部分想要注入的依赖对象
@Autowired
public void SetClassTest t(ClassTest classTest){
this.classTest = classTest;
}
可以实现多次重复注入,这在构造器注入是无法实现的
总结来说就是
- 字段注入应该避免,因为对象无法脱离容器而独立运行
- 构造器注入适用于强制对象注入
- setter注入适合于可选对象注入
- 数据库优化 , 索引
- mybatis 的源码有了解吗,他的工作流程