面试总结最新版 - wtdig/study GitHub Wiki

面试总结/2019.4.13

一、面试第一天

1、spring的事务

spring的事务的传播机制详解

外围开启事务,内部方法就算将异常处理了,也会回滚

外面没有开启事务,内部2个方法没有异常,最后抛异常了,不会回滚的,因为外围没有事务

事务的隔离级别:

1、读未提交的  A向B的账户转入100元,此时没有提交事务,B查询,发现有100元,结果A发现有错误,修改成50元,提交事务,B再次查询的时候,发现
金额不对,此时就是读未提交的事务,这样就产生了脏读;

2、读已提交的  A去读取账户的余额,发现有钱,此时,B将账户的余额消费了,提交了事务,A买东西,扣款时,发现余额不足,就纳闷了;此时,就出现了不可重复读

3、可重复读  A当拿着工资卡去消费时,一旦系统开始读取工资卡信息(即事务开始),B就不可能对该记录进行修改,也就是B不能在此时转账,这样就避免了不可重复读

4、串行化  事务的最高级别,可以避免脏读、不可重复读,幻读

注意区别:

不可重复读和幻读的区别:

1、不可重复读的重点是修改:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
2、幻读的重点在于新增或者删除:同样的条件, 第1次和第2次读出来的记录数不一样

当然, 从总的结果来看, 似乎两者都表现为两次读取的结果不一致.
但如果你从控制的角度来看, 两者的区别就比较大
对于前者, 只需要锁住满足条件的记录
对于后者, 要锁住满足条件及其相近的记录

2、mysql的锁

共享锁(S锁):用于只读操作(SELECT),锁定共享的资源。共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。

更新锁(U锁):用于可更新的资源中。防止当多个会话在读取、锁定以及随后可能进行的资源更新时发生常见形式的死锁。

独占锁(X锁,也叫排他锁):一次只能有一个独占锁用在一个资源上,并且阻止其他所有的锁包括共享缩。写是独占锁,可以有效的防止“脏读”。

3、sql查询
4、类加载顺序
5、设计模式
享元模式: 比如常量池之类的

设计模式的分类:

创建型模式:工厂模式、抽象工厂模式、单例模式、建造者模式;

结构型模式:适配器模式、桥接模式、装饰模式、外观模式、享元模式、代理模式;

行为型模式:命令模式、中介者模式、观察者模式、状态模式、策略模式

6、java中锁的处理

参考资料

可重入锁:

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Reentrant Lock重新进入锁。对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

public sychrnozied void test() {
    xxxxxx;
    test2();
}
 
public sychronized void test2() {
    yyyyy;
}
在上面代码段中,执行 test 方法需要获得当前对象作为监视器的对象锁,但方法中又调用了 test2 的同步方法。

如果锁是具有可重入性的话,那么该线程在调用 test2 时并不需要再次获得当前对象的锁,可以直接进入 test2 方法进行操作。
如果锁是不具有可重入性的话,那么该线程在调用 test2 前会等待当前对象锁的释放,实际上该对象锁已被当前线程所持有,不可能再次获得。
如果锁是不具有可重入性特点的话,那么线程在调用同步方法、含有锁的方法时就会产生死锁。
7、springmvc的注解
1、获取请求的参数
@RequestParam(value = "itemId", required = true, defaultValue = "1")

2、获取restful风格的参数

@RequestMapping("item/{id}")
@ResponseBody
public Item queryItemById(@PathVariable() Integer id) {
	Item item = this.itemService.queryItemById(id);
	return item;
}
8、锁的实现

jvm的加锁原理: synchronize关键字的底层原理

AQS

CAS的加锁原理:

当前对象的变量值V

预期值A

要期望修改的值B

如果V的值等于预期值A,就将变量V的值设置成B值,否则不进行处理

CAS的原理很简单,包含三个值当前内存值(V)、预期原来的值(A)以及期待更新的值(B)。

如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B,返回true。否则处理器不做任何操作,返回false。

实现CAS最重要的一点,就是比较和交换操作的一致性,否则就会产生歧义。

比如当前线程比较成功后,准备更新共享变量值的时候,这个共享变量值被其他线程更改了,那么CAS函数必须返回false。

cas会出现ABA的问题:

ABA问题
这种操作无法涵盖互斥同步的所有使用场景,漏洞在于:如果一个V初次读取的时候是A,且在准备赋值的时候还是A,我们能说它的值没有被其他线程改变过么?如果在这期间曾改为B,又改回A,那CAS就会误认为它从来没有被改变过。这个漏洞就是CAS操作的ABA问题。

优化方案
J.U.C包提供一个带有标记的原子引用类AtomicStanmpedReference,
可以通过控制变量值的版本来保证CAS的正确性。但大部分情况下,ABA问题都不会影响程序的并发性,
需要解决的话,改用传统的互斥同步可能会比原子类更高效

9、mysql的索引

索引参考资料

索引失效参考资料

1、索引无法存储null值

2、不适合键值较少的列(重复数据较多的列)

3、前导模糊查询不能利用索引(like '%XX'或者like '%XX%')

4、索引失效的几种情况

  1.如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)

  要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引

  2.对于多列索引,不是使用的第一部分,则不会使用索引

  3.like查询以%开头

  4.如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引

  5.如果mysql估计使用全表扫描要比使用索引快,则不使用索引

普通索引
唯一索引
与普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值(注意和主键不同)。如果是组合索引,则列值的组合必须唯一,创建方法和普通索引类似

单列索引、多列索引

多个单列索引与单个多列索引的查询效果不同,因为执行查询时,MySQL只能使用一个索引,会从多个索引中选择一个限制最为严格的索引。

组合索引(最左前缀)

平时用的SQL查询语句一般都有比较多的限制条件,所以为了进一步榨取MySQL的效率,就要考虑建立组合索引。例如上表中针对title和time建立一个组合索引:ALTER TABLE article ADD INDEX index_titme_time (title(50),time(10))。建立这样的组合索引,其实是相当于分别建立了下面两组组合索引:

–title,time

–title

为什么没有time这样的组合索引呢?这是因为MySQL组合索引“最左前缀”的结果。简单的理解就是只从最左面的开始组合。并不是只要包含这两列的查询都会用到该组合索引,如下面的几个SQL所示:

1	–使用到上面的索引
2	SELECT * FROM article WHREE title='测试' AND time=1234567890;
3	SELECT * FROM article WHREE utitle='测试';
4	–不使用上面的索引
5	SELECT * FROM article WHREE time=1234567890;

因此索引也会有它的缺点:虽然索引大大提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件。建立索引会占用磁盘空间的索引文件。

索引分为聚簇索引和非聚簇索引两种,聚簇索引是按照数据存放的物理位置为顺序的,而非聚簇索引就不一样了;聚簇索引能提高多行检索的速度,而非聚簇索引对于单行的检索很快。

索引不会包含有NULL值的列

只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。

MySQL查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作;尽量不要包含多个列的排序,如果需要最好给这些列创建复合索引。

like语句操作
一般情况下不鼓励使用like操作,如果非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。

不要在列上进行运算

例如:select * from users where YEAR(adddate)<2007,将在每个行上进行运算,这将导致索引失效而进行全表扫描,因此我们可以改成:select * from users where adddate<’2007-01-01′。

最后总结一下,MySQL只对一下操作符才使用索引:<,<=,=,>,>=,between,in,以及某些时候的like(不以通配符%或_开头的情形)。而理论上每张表里面最多可创建16个索引,不过除非是数据量真的很多,否则过多的使用索引也不是那么好玩的,比如我刚才针对text类型的字段创建索引的时候,系统差点就卡死了。

1、选择索引的数据类型

MySQL支持很多数据类型,选择合适的数据类型存储数据对性能有很大的影响。通常来说,可以遵循以下一些指导原则:

(1)越小的数据类型通常更好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快。
(2)简单的数据类型更好:整型数据比起字符,处理开销更小,因为字符串的比较更复杂。在MySQL中,应该用内置的日期和时间数据类型,而不是用字符串来存储时间;以及用整型数据类型存储IP地址。
(3)尽量避免NULL:应该指定列为NOT NULL,除非你想存储NULL。在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值。

1.1、选择标识符
选择合适的标识符是非常重要的。选择时不仅应该考虑存储类型,而且应该考虑MySQL是怎样进行运算和比较的。一旦选定数据类型,应该保证所有相关的表都使用相同的数据类型。
(1)    整型:通常是作为标识符的最好选择,因为可以更快的处理,而且可以设置为AUTO_INCREMENT。

(2)    字符串:尽量避免使用字符串作为标识符,它们消耗更好的空间,处理起来也较慢。而且,通常来说,字符串都是随机的,所以它们在索引中的位置也是随机的,这会导致页面分裂、随机访问磁盘,聚簇索引分裂(对于使用聚簇索引的存储引擎)。

2、索引入门
对于任何DBMS,索引都是进行优化的最主要的因素。对于少量的数据,没有合适的索引影响不是很大,但是,当随着数据量的增加,性能会急剧下降。
如果对多列进行索引(组合索引),列的顺序非常重要,MySQL仅能对索引最左边的前缀进行有效的查找。例如:
假设存在组合索引it1c1c2(c1,c2),查询语句select * from t1 where c1=1 and c2=2能够使用该索引。查询语句select * from t1 where c1=1也能够使用该索引。但是,查询语句select * from t1 where c2=2不能够使用该索引,因为没有组合索引的引导列,即,要想使用c2列进行查找,必需出现c1等于某值。

2.1、索引的类型
索引是在存储引擎中实现的,而不是在服务器层中实现的。所以,每种存储引擎的索引都不一定完全相同,并不是所有的存储引擎都支持所有的索引类型。
2.1.1、B-Tree索引
假设有如下一个表:

二、面试第二天

1、数据库死锁的实现

1)事务之间对资源访问顺序的交替
一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。

解决方案:保证操作的一致性

2)并发修改同一记录

出现原因: 
 用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁由于比较隐蔽,但在稍大点的项目中经常发生。 

解决方案:

使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

2、线程池相关参数,常问的
核心线程数、最大线程数、空闲时间、使用的阻塞队列、拒绝策略的选择;

ArrayListBolockQue:有界队列,有指定大小

LinkListBolockQue:无界队列,没有指定大小

3、servlet的生命周期、jsp
jsp和servlet的区别和联系:
1.jsp经编译后就变成了Servlet.
(JSP的本质就是Servlet,JVM只能识别java的类,不能识别JSP的代码,Web容器将JSP的代码编译成JVM能够识别的java类)
2.jsp更擅长表现于页面显示,servlet更擅长于逻辑控制.
3.Servlet中没有内置对象,Jsp中的内置对象都是必须通过HttpServletRequest对象,HttpServletResponse对象以及HttpServlet对象得到.
Jsp是Servlet的一种简化,使用Jsp只需要完成程序员需要输出到客户端的内容,Jsp中的Java脚本如何镶嵌到一个类中,由Jsp容器完成。
而Servlet则是个完整的Java类,这个类的Service方法用于生成对客户端的响应。


联系:  
JSP是Servlet技术的扩展,本质上就是Servlet的简易方式。JSP编译后是“类servlet”。
Servlet和JSP最主要的不同点在于:
Servlet的应用逻辑是在Java文件中,并且完全从表示层中的HTML里分离开来。
而JSP的情况是Java和HTML可以组合成一个扩展名为.jsp的文件。
JSP侧重于视图,Servlet主要用于控制逻辑
Servlet更多的是类似于一个Controller,用来做控制。

理解以下三点即可:
1、不同之处在哪?
Servlet在Java代码中通过HttpServletResponse对象动态输出HTML内容
JSP在静态HTML内容中嵌入Java代码,Java代码被动态执行后生成HTML内容
2、各自的特点
Servlet能够很好地组织业务逻辑代码,但是在Java源文件中通过字符串拼接的方式生成动态HTML内容会导致代码维护困难、可读性差
JSP虽然规避了Servlet在生成HTML内容方面的劣势,但是在HTML中混入大量、复杂的业务逻辑同样也是不可取的
Servlet生命周期,即阐述Servlet从产生到毁灭的整个过程。 
        在Servlet产生到消亡的过程中,有三个生命周期函数,初始化方法init(),处理客户请求的方法service(),终止方法destroy()。
4、分布式事务
5、数组和链表的区别
数组是一个固定的大小,是连续的空间,(空间使用率低)查询某个元素,比较简单,删除和新增操作比较费时;

链表是可以扩展大小的,是不连续的空间,(空间利用率比较高),添加元素和删除元素快,查询元素慢;

6、按照什么规则进行分库分表
7、mysql查询的优化
limit  减少使用子查询  选择合适的存储引擎  建立索引
8、mysql引擎的区别

参考资料

1、MyISAM:默认表类型,它是基于传统的ISAM类型,ISAM是Indexed Sequential Access Method (有索引的顺序访问方法) 的缩写,它是存储记录和文件的标准方法。不是事务安全的,而且不支持外键,如果执行大量的select,insert MyISAM比较适合。

2、InnoDB:支持事务安全的引擎,支持外键、行锁、事务是他的最大特点。如果有大量的update和insert,建议使用InnoDB,特别是针对多个并发和QPS较高的情况。

MyISAM:

myisam只支持表级锁,用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。也可以通过lock table命令来锁表,这样操作主要是可以模仿事务,但是消耗非常大,一般只在实验演示中使用。

InnoDB :

Innodb支持事务和行级锁,是innodb的最大特色。

二、面试第二天

1、zookeeper实现分布式锁

zookeeper原理

参考资料

临时有序的节点
2、面试注意事项
1)、项目业务方面的总结:比如财务系统

2)、对某一个技术进行深入的挖掘
3、知识欠缺点
1、hashMap详细的底层原理: 扩容机制

2、大批量更新数据,避免锁定太多数据

比如: update table1 set ss = dd where type = 12 and name = 'wt';此时,会锁定太多的数据

可以先通过select id from table1 where type = 12 and name = 'wt',找到所有的id,如何在

update table1 set ss = dd where id in();

3、线程池相关参数

线程池创建核心线程池的依据:比如cpu的核数,使用的队形,是有界还是无界的

线程池使用完之后,要不要释放?

常见的4种线程池:
newFixedThreadPool() 
newSingleThreadExecutor() 
newCachedThreadPool() 
newScheduledThreadPool()

4、arrayList、hashmap为什么线程不安全

arrayList线程不安全,到底哪里不安全?

list在进行add的方法时,需要进行2步操作:

// 确定ArrayList的容量大小
ensureCapacity(size + 1); // Increments modCount!!
// 添加e到ArrayList中
elementData[size++] = e;

这条语句可拆分为两条:

  1. elementData[size] = e;
  2. size ++; 假设A线程执行完第一条语句时,CPU暂停执行A线程转而去执行B线程,此时ArrayList的size并没有加一,这时在ArrayList中B线程就会覆盖掉A线程赋的值,而此时,A线程和B线程先后执行size++,便会出现值为null的情况;至于结果三中出现的ArrayIndexOutOfBoundsException异常, 则是A线程在执行ensureCapacity(size+1)后没有继续执行,此时恰好minCapacity等于oldCapacity,B线程再去执行,同样由于minCapacity等于oldCapacity,ArrayList并没有增加长度,B线程可以继续执行赋值(elementData[size] = e)并size ++也执行了,此时,CPU又去执行A线程的赋值操作,由于size值加了1,size值大于了ArrayList的最大长度, 因此便出现了ArrayIndexOutOfBoundsException异常。

hashmap为什么线程不安全?

hashMap进行扩容的时候,会执行reHash()方法

假设这里有两个线程同时执行了put()操作,并进入了transfer()环节

while(null != e) { Entry<K,V> next = e.next; //线程1执行到这里被调度挂起了 e.next = newTable[i]; newTable[i] = e; e = next; }


5、线程的几种状态
sleep和wait的区别:
1,sleep方法是Thread类的静态方法,wait()是Object超类的成员方法
2,sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
3,sleep方法需要抛异常,wait方法不需要
为什么sleep方法需要抛异常,别问我为什么,因为:
Thread类中sleep方法就已经进行了抛异常处理
public static native void sleep(long millis) throws InterruptedException;
4,sleep方法可以在任何地方使用,
wait方法只能在同步方法和同步代码块中使用

6、MQ如何保证消息的丢失;

引入了消息确认机制,当消息处理完成后,给Server端发送一个确认消息,来告诉服务端可以删除该消息了,如果连接断开的时候,Server端没有收到消费者发出的确认信息,则会把消息转发给其他保持在线的消费者。

通过消息确认机制(Acknowlege)来实现,当消费者获取消息后,会想mq发送回执ACK,告知消息已经被接收;

7、MQ如何保证消费不重复消费;

将消息的唯一标识保存到外部介质中,每次消费处理时判断是否处理过;

8、搜索用的倒排索引

正常索引:

一首诗:作者:李白,诗词:窗前明月光、疑是地上霜;

建立李白索引,查询到光这个关键字;

而倒排索引:

建立一系列的索引:

窗
明月
地上
霜

反向索引到这所诗上

java基础

 Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。

  Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

  两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。 用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。
Union:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;

Union All:对两个结果集进行并集操作,包括重复行,不进行排序;
⚠️ **GitHub.com Fallback** ⚠️