《面试填坑系列》大厂常问Java高级面试100题解析(一)

955 阅读37分钟

Java高级面试100题及解析

数据库篇

  1. 事务四大特性(ACID)原子性、一致性、隔离性、持久性?
  2. 事务的并发?事务隔离级别,每个级别会引发什么问题,MySQL默认是哪个级别?
  3. MySQL常见的三种存储引擎(InnoDB、MyISAM、MEMORY)的区别?
  4. MySQL的MyISAM与InnoDB两种存储引擎在,事务、锁级别,各自的适用场景?
  5. MySQL B+Tree 索引和 Hash 索引的区别?
  6. sql 查询语句确定创建哪种类型的索引,如何优化查询
  7. 有哪些锁(乐观锁悲观锁),select 时怎么加排它锁?
  8. 数据库的读写分离、主从复制,主从复制分析的 7 个问题?
  9. MySQL 都有什么锁,死锁判定原理和具体场景,死锁怎么解决?
  10. MySQL 高并发环境解决方案?

Spring篇

  1. Spring IoC、AOP 原理
  2. Spring Bean 生命周期 
  3. Spring Bean 注入是如何解决循环依赖问题的
  4. 怎样用注解的方式配置 Spring?
  5. Spring 事务为何失效了
  6. SpringMVC 的流程?
  7. Springmvc 的优点:
  8. Spring 通知类型使用场景分别有哪些?
  9. IoC 控制反转设计原理?
  10. Spring 如何处理线程并发问题?

JVM篇

  1. Java 类加载过程?
  2. 描述一下 JVM 加载 Class 文件的原理机制?
  3. 简述 Java 垃圾回收机制。
  4. 什么是类加载器,类加载器有哪些?
  5. 如何判断一个对象是否存活?(或者 GC 对象的判定方法)
  6. 垃圾回收的优点和原理。并考虑 2 种回收机制。
  7. 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收
  8. Java 中会存在内存泄漏吗,请简单描述。
  9. 简述 Java 内存分配与回收策率以及 Minor GC 和 Major GC。
  10. Java 中垃圾收集的方法有哪些?

Java并发篇

  1. Synchronized 用过吗,其原理是什么?
  2. 为什么说 Synchronized 是非公平锁?
  3. 为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有
  4. 请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。
  5. 谈谈 ReadWriteLock 和 StampedLock。
  6. 如何让 Java 的线程彼此同步?你了解过哪些同步器?请分别介绍下。
  7. 线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?
  8. 提到可以通过配置不同参数创建出不同的线程池,那么 Java 中默认实现好的线程池又有哪些呢?请比
  9. 如何在 Java 线程池中提交线程?
  10. 请谈谈 volatile 有什么特点,为什么它能保证变量对所有线程的可见性?

Redis缓存篇

  1. 什么是 Redis 事务?原理是什么?
  2. 请介绍一下 Redis 的数据类型 SortedSet(zset)以及底层实现机制?
  3. Redis 常用的命令有哪些?
  4. 什么是缓存穿透?怎么解决?
  5. 什么是缓存雪崩? 怎么解决?
  6. 请介绍几个可能导致 Redis 阻塞的原因
  7. 缓存的更新策略有几种?分别有什么注意事项?
  8. Redis 为什么设计成单线程的?
  9. Redis 持久化机制 AOF 和 RDB 有哪些不同之处?
  10. Redis 缓存失效策略有哪些?

RabbitMQ篇

  1. RabbitMQ 的使用场景有哪些?
  2. RabbitMQ 有哪些重要的角色?
  3. RabbitMQ 有哪些重要的组件?
  4. RabbitMQ 的消息是怎么发送的?
  5. RabbitMQ 怎么保证消息的稳定性?
  6. RabbitMQ 怎么避免消息丢失?
  7. 要保证消息持久化成功的条件有哪些?
  8. RabbitMQ 有几种广播类型?
  9. RabbitMQ 怎么实现延迟消息队列?
  10. RabbitMQ 集群中唯一一个磁盘节点崩溃了会发生什么情况?

Java集合篇

  1. ArrayList 和 Vector 的区别
  2. 说说 ArrayList,Vector, LinkedList 的存储性能和特性
  3. 快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?
  4. hashmap 的数据结构。
  5. HashMap 的工作原理是什么?
  6. Hashmap 什么时候进行扩容呢?
  7. HashSet 和 TreeSet 有什么区别?
  8. HashSet 的底层实现是什么?
  9. LinkedHashMap 的实现原理?
  10. Collection 和 Collections 的区别。

微服务篇

  1. 使用Spring Cloud有什么优势?
  2. 服务注册和发现是什么意思?Spring Cloud如何实现?
  3. 负载平衡的意义什么?
  4. 什么是Hystrix?它如何实现容错?
  5. 什么是Hystrix断路器?我们需要它吗?
  6. 什么是Netflix Feign?它的优点是什么?
  7. Spring Boot 的核心配置文件有哪几个?它们的区别是什么?
  8. Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
  9. Spring Boot中的监视器是什么?
  10. 什么是Swagger?你用Spring Boot实现了它吗?

Zookeeper篇

  1. zk的命名服务(文件系统)
  2. zk的配置管理(文件系统、通知机制)
  3. Zookeeper集群管理(文件系统、通知机制)
  4. Zookeeper分布式锁(文件系统、通知机制)
  5. 获取分布式锁的流程
  6. Zookeeper队列管理(文件系统、通知机制)
  7. Zookeeper数据复制
  8. Zookeeper工作原理
  9. zookeeper是如何保证事务的顺序一致性的?
  10. Zookeeper 下 Server工作状态

解决方案篇

本次更新Java 面试题(一)的1~20题答案

数据库篇

1. 事务四大特性(ACID)原子性、一致性、隔离性、持久性?

1.1. 原子性(Atomicity)

原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

1.2. 一致性(Consistency)

事务开始前和结束后,数据库的完整性约束没有被破坏。比如AB转账,不可能A扣了钱,B却没收到

1.3. 隔离性(Isolation)

隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离

同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

1.4.持久性(Durability)

持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作

2.事务的并发?事务隔离级别,每个级别会引发什么问题,MySQL默认是哪个级别?

从理论上来说,事务应该彼此完全隔离,以避免并发事务所导致的问题,然而,那样会对性能产生极大的影响,因为事务必须按顺序运行,在实际开发中,为了提升性能,事务会以较低的隔离级别运行,事务的隔离级别可以通过隔离事务属性指定

2.1事务的并发问题

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

2、不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果因此本事务先后两次读到的数据结果会不一致。

3、幻读:幻读解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。

例如:事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有跟没有修改一样,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。

2.2事务的隔离级别

事务隔离级别

脏读

不可重复读

幻读

读未提交 read-uncommitted

不可重复读 read-committed

可重复读 repeatable-read

串行化 serializable

读未提交:另一个事务修改了数据,但尚未提交,而本事务中的SELECT会读到这些未被提交的数据脏读

不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果因此本事务先后两次读到的数据结果会不一致。

可重复读:在同一个事务里,SELECT的结果是事务开始时时间点的状态,因此,同样的
SELECT操作读到的结果会是一致的。但是,会有幻读现象

串行化:最高的隔离级别,在这个隔离级别下,不会产生任何异常。并发的事务,就像事务是在一个个按照顺序执行一样

MySQL默认的事务隔离级别为repeatable-read 

MySQL支持4中事务隔离级别

事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持. 

Oracle支持的2种事务隔离级别:READ_COMMITED , SERIALIZABLE

2.3补充

1. SQL规范所规定的标准,不同的数据库具体的实现可能会有些差异

2. MySQL中默认事务隔离级别是“可重复读”时并不会锁住读取到的行

  • 事务隔离级别未提交读时,写数据只会锁住相应的行。
  • 事务隔离级别为可重复读时,写数据会锁住整张表。
  • 事务隔离级别为串行化时,读写数据都会锁住整张表。

隔离级别越高越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed
,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

3. MySQL常见的三种存储引擎(InnoDB、MyISAM、MEMORY)的区别?

MySQL 存储引擎 MyISAM 与 InnoDB 如何选择

MySQL 有多种存储引擎,每种存储引擎有各自的优缺点,可以择优选择使用:MyISAM、InnoDB、MERGE、MEMORY(HEAP)、BDB(BerkeleyDB)、EXAMPLE、FEDERATED、ARCHIVE、CSV、BLACKHOLE

虽然 MySQL 里的存储引擎不只是 MyISAM 与 InnoDB 这两个,但常用的就是两个

两种存储引擎的大致区别表现在

  • InnoDB 支持事务,MyISAM 不支持,这一点是非常之重要。事务是一种高级的处理方式,如在一些列增删改中只要哪个出错还可以回滚还原,而 MyISAM 就不可以了。
  • MyISAM 适合查询以及插入为主的应用
  • InnoDB 适合频繁修改以及涉及到安全性较高的应用
  • InnoDB 支持外键,MyISAM 不支持。
  • 从 MySQL5.5.5 以后,InnoDB 是默认引擎
  • InnoDB 不支持 FULLTEXT 类型的索引。
  • InnoDB 中不保存表的行数,如select count(*) from table时,InnoDB 需要扫描一遍整个表来计算有多少行,但是 MyISAM 只要简单的读出保存好的行数即可。注意的是,当 count(*)语句包含 where 条件时 MyISAM 也需要扫描整个表。
  • 对于自增长的字段,InnoDB 中必须包含只有该字段的索引,但是在 MyISAM 表中可以和其他字段一起建立联合索引。
  • DELETE FROM table时,InnoDB 不会重新建立表,而是一行一行的 删除,效率非常慢MyISAM 则会重建表
  • InnoDB 支持行锁(某些情况下还是锁整表,如 update table set a=1 where user like '%lee%'

关于 MySQL 数据库提供的两种存储引擎,MyISAM 与 InnoDB 选择使用:

  • INNODB 会支持一些关系数据库的高级功能如事务功能和行级锁,MyISAM 不支持
  • MyISAM 的性能更优,占用的存储空间少,所以,选择何种存储引擎,视具体应用而定。
  • 如果你的应用程序一定要使用事务,毫无疑问你要选择 INNODB 引擎。但要注意,INNODB 的行级锁是有条件的。在 where 条件没有使用主键时,照样会锁全表。比如 DELETE FROM mytable 这样的删除语句。
  • 如果你的应用程序对查询性能要求较高,就要使用 MyISAM 了MyISAM 索引和数据是分开的,而且其索引是压缩的,可以更好地利用内存。所以它的查询性能明显优于 INNODB。压缩后的索引也能节约一些磁盘空间。MyISAM 拥有全文索引的功能,这可以极大地优化 LIKE 查询的效率

有人说 MyISAM 只能用于小型应用,其实这只是一种偏见。如果数据量比较大,这是需要通过升级架构来解决,比如分表分库,而不是单纯地依赖存储引擎。

现在一般都是选用 innodb 了,主要是 MyISAM 的全表锁,读写串行问题,并发效率锁表,效率低,MyISAM 对于读写密集型应用一般是不会去选用的。

4. MySQL的MyISAMInnoDB两种存储引擎在,事务、锁级别,各自的适用场景?

事务处理上方面

  • MyISAM强调的是性能,每次查询具有原子性,其执行数度比 InnoDB 类型更快,但是不提供事务支持
  • InnoDB提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。

锁级别

  • MyISAM只支持表级锁,用户在操作 MyISAM 表时,select,update,delete,insert 语句都会给表自动加锁,如果加锁以后的表满足 insert 并发的情况下,可以在表的尾部插入新的数据。
  • InnoDB:支持事务和行级锁,是 innodb 的最大特色。行锁大幅度提高了多用户并发操作的新能。但是 InnoDB 的行锁,只是在 WHERE 的主键是有效的,非主键的 WHERE 都会锁全表的。

5.MySQL B+Tree 索引和 Hash 索引的区别?

  • Hash 索引结构的特殊性,其检索效率非常高,索引的检索可以一次定位;
  • B+树索引需要从根节点到枝节点,最后才能访问到页节点这样多次的 IO 访问;

那为什么大家不都用 Hash 索引而还要使用 B+树索引呢?

Hash 索引

  1. Hash 索引仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和 Hash 运算前完全一样;
  2. Hash 索引无法被用来避免数据的排序操作,因为 Hash 值的大小关系并不一定和 Hash 运算前的键值完全一样;
  3. Hash 索引不能利用部分索引键查询,对于组合索引,Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算 Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候,Hash 索引也无法被利用;
  4. Hash 索引在任何时候都不能避免表扫描,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要回表查询数据;
  5. Hash 索引遇到大量 Hash 值相等的情况后性能并不一定就会比 B+树索引高。

B+Tree 索引

MySQL 中,只有 HEAP/MEMORY 引擎才显示支持 Hash 索引

常用的 InnoDB 引擎中默认使用的是 B+树索引,它会实时监控表上索引的使用情况,如果认为建立哈希索引可以提高查询效率,则自动在内存中的“自适应哈希索引缓冲区”建立哈希索引(在 InnoDB 中默认开启自适应哈希索引),通过观察搜索模式,MySQL 会利用 index key 的前缀建立哈希索引,如果一个表几乎大部分都在缓冲池中,那么建立一个哈希索引能够加快等值查询。

B+树索引和哈希索引的明显区别是:

如果是等值查询,那么哈希索引明显有绝对优势因为只需要经过一次算法即可找到相应的键值;当然了,这个前提是,键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据

如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索;

同理,哈希索引没办法利用索引完成排序,以及 like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询);

哈希索引也不支持多列联合索引的最左匹配规则

B+树索引的关键字检索效率比较平均,不像 B 树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在所谓的哈希碰撞问题

在大多数场景下,都会有范围查询、排序、分组等查询特征,用 B+树索引就可以了

6.sql 查询语句确定创建哪种类型的索引,如何优化查询

  • 性能优化过程中,选择在哪个列上创建索引是最重要的步骤之一,可以考虑使用索引的主要有两种类型的列:在 where 子句中出现的列,在 join 子句中出现的列。
  • 考虑列中值的分布,索引的列的基数越大,索引的效果越好。
  • 使用短索引,如果对字符串列进行索引,应该指定一个前缀长度,可节省大量索引空间,提升查询速度。
  • 利用最左前缀,顾名思义,就是最左优先,在多列索引,有体现:(ALTER TABLE people ADD INDEX lname
    fname
    age (lame,fname,age);),所谓最左前缀原则就是先要看第一列,在第一列满足的条件下再看左边第二列,以此类推
  • 不要过度建索引,只保持所需的索引。每个额外的索引都要占用额外的磁盘空间,并降低写操作的性能
  • 在修改表的内容时,索引必须进行更新,有时可能需要重构,因此,索引越多,所花的时间越长
  • MySQL 只对一下操作符才使用索引<,<=,=,>,>=,between,in
  • 以及某些时候的 like(不以通配符%或_开头的情形)。

7.有哪些锁(乐观锁悲观锁),select 时怎么加排它锁?

悲观锁(Pessimistic Lock)

悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的 select … for update 操作来实现悲观锁。当数据库执行 select for update 时会获取被 select 中的数据行的行锁,因此其他并发执行的 select for update 如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update 获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。

这里需要注意的一点是不同的数据库对 select for update 的实现和支持都是有所区别的,例如 oracle 支持 select for update no wait,表示如果拿不到锁立刻报错,而不是等待,MySQL 就没有 no wait 这个选项。另外MySQL 还有个问题是 select for update 语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在 MySQL 中用悲观锁务必要确定走了索引,而不是全表扫描

乐观锁(Optimistic Lock)

乐观锁,也叫乐观并发控制,它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,那么当前正在提交的事务会进行回滚

乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。

乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持一般的做法是在需要锁的数据上增加一个版本号,或者时间戳,然后按照如下方式实现:

乐观锁(给表加一个版本号字段) 这个并不是乐观锁的定义,给表加版本号,是数据库实现乐观锁的一种方式

1\. SELECT data AS old_data, version AS old_version FROM …;
2\. 根据获取的数据进行业务操作,得到 new_data 和 new_version
3\. UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
    // 乐观锁获取成功,操作完成
} else {
    // 乐观锁获取失败,回滚并重试
}

乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能

乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方

总结

悲观锁和乐观锁是数据库用来保证数据并发安全防止更新丢失的两种方法,例子在select ... for update前加个事务就可以防止更新丢失。悲观锁和乐观锁大部分场景下差异不大,一些独特场景下有一些差别,一般我们可以从如下几个方面来判断。

  • 响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。

  • 冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率,如果冲突频率大,乐观锁会需要多次重试才能成功,代价比较大。

  • 重试代价:如果重试代价大,建议采用悲观锁。

8.数据库的读写分离、主从复制,主从复制分析的 7 个问题?

主从复制的几种方式

同步复制

  • 所谓的同步复制,意思是 master 的变化,必须等待 slave-1,slave-2,...,slave-n 完成后才能返回。 这样,显然不可取,也不是 MySQL 复制的默认设置。比如,在 WEB 前端页面上,用户增加了条记录,需要等待很长时间。

异步复制

  • 如同 AJAX 请求一样。master 只需要完成自己的数据库操作即可。至于 slaves 是否收到二进制日志,是否完成操作,不用关心,MySQL 的默认设置。

半同步复制

  • master 只保证 slaves 中的一个操作成功,就返回,其他 slave 不管。 这个功能,是由 google 为 MySQL 引入的。

主从复制分析的 7 个问题

问题 1:master 的写操作,slaves 被动的进行一样的操作,保持数据一致性,那么 slave 是否可以主动的进行写操作?

假设 slave 可以主动的进行写操作,slave 又无法通知 master,这样就导致了 master 和 slave 数据不一致了。因此slave 不应该进行写操作,至少是 slave 上涉及到复制的数据库不可以写。实际上,这里已经揭示了读写分离的概念

问题 2:主从复制中,可以有 N 个 slave,可是这些 slave 又不能进行写操作,要他们干嘛?

以实现数据备份

类似于高可用的功能,一旦 master 挂了,可以让 slave 顶上去,同时 slave 提升为 master

异地容灾,比如 master 在北京,地震挂了,那么在上海的 slave 还可以继续。

主要用于实现 scale out,分担负载,可以将读的任务分散到 slaves 上

很可能的情况是,一个系统的读操作远远多于写操作,因此写操作发向 master,读操作发向 slaves 进行操作

问题 3:主从复制中有 master,slave1,slave2,...等等这么多 MySQL 数据库,那比如一个 JAVA WEB 应用到底应该连接哪个数据库?

当 然,我们在应用程序中可以这样,insert/delete/update这些更新数据库的操作,用connection(for master)进行操作,select 用 connection(for slaves)进行操作。那我们的应用程序还要完成怎么从 slaves 选择一个来执行 select,例如使用简单的轮循算法

这样的话,相当于应用程序完成了 SQL 语句的路由,而且与 MySQL 的主从复制架构非常关联,一旦 master 挂了,某些 slave 挂了,那么应用程序就要修改了。能不能让应用程序与 MySQL 的主从复制架构没有什么太多关系呢?

找一个组件application program 只需要与它打交道,用它来完成 MySQL 的代理,实现 SQL 语句的路由

MySQL proxy 并不负责,怎么从众多的 slaves 挑一个?可以交给另一个组件(比如 haproxy)来完成。

这就是所谓的MySQL READ WRITE SPLITE,MySQL的读写分离。

问题 4:如果 MySQL proxy , direct , master 他们中的某些挂了怎么办?

总统一般都会弄个副总统,以防不测。同样的,可以给这些关键的节点来个备份。

问题 5:当 master 的二进制日志每产生一个事件,都需要发往 slave,如果我们有 N 个 slave,那是发 N 次,还是只发一次?

如果只发一次,发给了 slave-1,那 slave-2,slave-3,...它们怎么办?

显 然,应该发 N 次。实际上,在 MySQL master 内部,维护 N 个线程,每一个线程负责将二进制日志文件发往对应的 slave。master 既要负责写操作,还的维护 N 个线程,负担会很重。可以这样,slave-1 是 master 的从,slave-1 又是 slave-2,slave-3,...的主,同时 slave-1 不再负责 select。 slave-1 将 master 的复制线程的负担,转移到自己的身上这就是所谓的多级复制的概念

问题 6:当一个 select 发往 MySQL proxy,可能这次由 slave-2 响应,下次由 slave-3 响应,这样的话,就无法利用查询缓存了。

应该找一个共享式的缓存,比如 memcache 来解决。将 slave-2,slave-3,...这些查询的结果都缓存至 mamcache 中。

问题 7:随着应用的日益增长,读操作很多,我们可以扩展 slave,但是如果 master 满足不了写操作了,怎么办呢?

scale on ?更好的服务器? 没有最好的,只有更好的,太贵了。。。

scale out ? 主从复制架构已经满足不了。

可以分库【垂直拆分】,分表【水平拆分】。

9.MySQL 都有什么锁,死锁判定原理和具体场景,死锁怎么解决?

MySQL 都有什么锁

MySQL 有三种锁的级别:页级、表级、行级

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

什么情况下会造成死锁

  • 所谓死锁: 是指两个或两个以上的进程在执行过程中。
  • 因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
  • 此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等竺的进程称为死锁进程。
  • 表级锁不会产生死锁.所以解决死锁主要还是针对于最常用的 InnoDB。

死锁的关键在于:两个(或以上)的 Session 加锁的顺序不一致。

那么对应的解决死锁问题的关键就是:让不同的 session加锁有次序

死锁的解决办法

  • 查出的线程杀死 kill
SELECT trx_MySQL_thread_id FROM information_schema.INNODB_TRX;
  • 设置锁的超时时间

Innodb 行锁的等待时间,单位秒。可在会话级别设置,RDS 实例该参数的默认值为 50(秒)。

生产环境不推荐使用过大的 innodb_lock_wait_timeout参数值

该参数支持在会话级别修改,方便应用在会话级别单独设置某些特殊操作的行锁等待超时时间,如下:

set innodb_lock_wait_timeout=1000; —设置当前会话 Innodb 行锁等待超时时间,单位秒。

10.MySQL 高并发环境解决方案?

MySQL 高并发环境解决方案 分库 分表 分布式 增加二级缓存。。。。。

需求分析:互联网单位 每天大量数据读取,写入,并发性高。

  • 现有解决方式:水平分库分表,由单点分布到多点数据库中,从而降低单点数据库压力。
  • 集群方案:解决 DB 宕机带来的单点 DB 不能访问问题。
  • 读写分离策略:极大限度提高了应用中 Read 数据的速度和并发量。无法解决高写入压力。

Spring全家桶

1.Spring IoC、AOP 原理

1.1.定义

1.1.1.IoC

Inversion of Control,控制反转。是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(DependencyInjection,简称 DI),这也是 Spring 的实现方式。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。 

1.1.2.AOP

Aspect Oriented Programming,面向切面编程。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。其中,最常用的使用场景一般有日志模块、权限模块、事物模块。

1.2.原理

1.2.1.IoC

IoC 内部核心原理就是反射技术,当然这里面还涉及到 Bean 对象的初始化构建等步骤,这个在后面的生命周期中讲,这里我们需要了解 Java 中反射是如何做的就好。这里主要说明下主要的相关类和可能面试问题转向,具体的 API 实现需要自己去看。 

还有其他的类不一一列举出来,都在 java.lang.reflect 包下。说到这个模块的时候,那么面试官可能会考察相关的知识,主要是考察你是否真的有去了解过反射的使用。举两个例子: 

利用反射获取实例的私有属性值怎么做

这里其实就是里面的重要考察点就是反射对私有属性的处理。

/**
 * 通过反射获取私有的成员变量.
 */
private Object getPrivateValue(Person person, String fieldName)
{
    try
    {
        Field field = person.getClass().getDeclaredField(fieldName);
        // 主要就是这里,需要将属性的 accessible 设置为 true 
        field.setAccessible(true);
        return field.get(person);
    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
    return null;
}

如何通过反射构建对象实例?

使用默认构造函数(无参)创建的话: 

Class.newInstance() Constroctor constroctor = clazz.getConstructor(String.class,Integer.class); Object obj = constroctor.newInstance("name", 18);

1.2.2.AOP

AOP 的内部原理其实就是动态代理和反射了。主要涉及到的反射类: 


动态代理相关原理的话,你需要了解什么是代理模式、静态代理的不足、动态代理的实现原理。Spring 中实现动态代理有两种方式可选,这两种动态代理的实现方式的一个对比也
是面试中常问的。 

JDK 动态代理 

必须实现 InvocationHandler 接口,然后通过 Proxy.newProxyInstance(ClassLoader
loader, Class<?>[] interfaces, InvocationHandler h) 获得动态代理对象。
CGLIB 动态代理

使用 CGLIB 动态代理,被代理类不需要强制实现接口。CGLIB 不能对声明为 final的方法进行代理,因为 CGLIB 原理是动态生成被代理类的子类。

OK,AOP 讲了。其实讲到这里,可能会有一个延伸的面试问题。我们知道,Spring事物也是 通 过 AOP 来 实 现的 , 我们使用的时候 一 般就是在方法上 加@Tranactional 注解,那么你有没有遇到过事物不生效的情况呢?这是为什么?这个问题我们在后面的面试题中会讲。 

2.Spring Bean 生命周期 


这只是个大体流程,内部的具体行为太多,需要自行去看看代码。 

3.Spring Bean 注入是如何解决循环依赖问题的 

3.1. 什么是循环依赖,有啥问题? 

循环依赖就是 N 个类中循环嵌套引用,这样会导致内存溢出。循环依赖主要分两种:

  • 构造器循环依赖
  • setter 循环依赖
3.2. Spring 解决循环依赖问题 
  • 构造器循环依赖问题 
无解,直接抛出 BeanCurrentlyInCreatingException 异常。
  • setter 循环依赖问题 
单例模式下,通过“三级缓存”来处理。非单例模式的话,问题无解。 

Spring 初始化单例对象大体是分为如下三个步骤的:

  • createBeanInstance:调用构造函数创建对象
  • populateBean:调用类的 setter 方法填充对象属性
  • initializeBean:调用定义的 Bean 初始化 init 方法
可以看出,循环依赖主要发生在 1、2 步,当然如果发生在第一步的话,Spring 也是无法解决该问题的。那么就剩下第二步 populateBean 中出现的循环依赖问题。通过“三级缓存”来处理,三级缓存如下:

  • singletonObjects:Cache of singleton objects: bean name --> bean instance,完成初始化的单例对象的 cache(一级缓存)
  • earlySingletonObjects:Cache of early singleton objects: bean name--> bean instance ,完成实例化但是尚未初始化的,提前暴光的单例对象的 cache (二级缓存)
  • singletonFactories : Cache of singleton factories: bean name -->ObjectFactory,进入实例化阶段的单例对象工厂的 cache (三级缓存) 

我们看下获取单例对象的方法:

protected Object getSingleton(String beanName, boolean allowEarlyReference)
{
    Object singletonObject = this.singletonObjects.get(beanName);
    // isSingletonCurrentlyInCreation:判断当前单例 bean 是否正在创建中 
    if(singletonObject == null && isSingletonCurrentlyInCreation(beanName))
    {
        synchronized(this.singletonObjects)
        {
            singletonObject = this.earlySingletonObjects.get(beanName);
            // allowEarlyReference:是否允许从 singletonFactories 中通过 getObject 拿到 
            对象
            if(singletonObject == null && allowEarlyReference)
            {
                ObjectFactory <? > singletonFactory = this.singletonFactories.get(beanName);
                if(singletonFactory != null)
                {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return(singletonObject != NULL_OBJECT ? singletonObject : null);
}

其中解决循环依赖问题的关键点就在 singletonFactory.getObject() 这一步,getObject 这是 ObjectFactory<T> 接口的方法。Spring 通过对该方法的实现,在createBeanInstance 之后,populateBean 之前,通过将创建好但还没完成属性设置和初始化的对象提前曝光,然后再获取 Bean 的时候去看是否有提前曝光的对象实例来判断是否要走创建流程。

protected void addSingletonFactory(String beanName, ObjectFactory <? > singletonFactory)
{
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized(this.singletonObjects)
    {
        if(!this.singletonObjects.containsKey(beanName))
        {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

4.Spring 事务为何失效了

可能的原因:

  1. MySQL 使用的是 MyISAM 引擎,而 MyISAM 是不支持事务的。需要支持使用可以使用 InnoDB 引擎
  2. 如果使用了 Spring MVC ,context:component-scan 重复扫描问题可能会引起事务失败
  3. @Transactional 注解开启配置放到 DispatcherServlet 的配置里了。
  4. @Transactional 注解只能应用到 public 可见度的方法上。 在其他可见类型上声明,事务会失效。
  5. 在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。所以如果强制使用了 CGLIB,那么事物会实效。
  6. @Transactional 同一个类中无事务方法 a() 内部调用有事务方法 b(),那么此时事物不生效。 
按 理 说 , 如 果 按 照 Spring 说 的 事 物 传 播 级 别 去 配 置 其 事 物 级 别 为REQUIRES_NEW 的话,那么应该是在调用 b() 的时候会新生成一个事物。实际上却没有。



NOT_SUPPORTED 总是非事务地执行,并挂起任何存在的事务其实,这是由于 Spring 的事物实现是通过 AOP 来实现的。此时,当这个有注解的方法 b() 被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动 transaction。然而,如果这个有注解的方法是被同一个类中的其他方法 a() 调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个 bean,所以就不会启动 transaction,我们看到的现象就是 @Transactional 注解无效。 

5.怎样用注解的方式配置 Spring?

Spring 在 2.5 版本以后开始支持用注解的方式来配置依赖注入。可以用注解的方式来替代XML 方式的 bean 描述,可以将 bean 描述转移到组件类的内部,只需要在相关类上、方法上或者字段声明上使用注解即可。注解注入将会被容器在 XML 注入之前被处理,所以后者会覆盖掉前者对于同一个属性的处理结果。注解装配在 Spring 中是默认关闭的。所以需要在 Spring 文件中配置一下才能使用基于注解的装配模式。如果你想要在你的应用程序中使用关于注解的方法的话,请参考如下的配置。 

<beans>
    <context:annotation-config/>
    <!-- bean definitions go here -->
</beans>

在标签配置完成以后,就可以用注解的方式在 Spring 中向属性、方法和构造方法中自动装配变量。

下面是几种比较重要的注解类型:

  • @Required:该注解应用于设值方法。
  • @Autowired:该注解应用于有值设值方法、非设值方法、构造方法和变量。
  • @Qualifier:该注解和@Autowired 注解搭配使用,用于消除特定 bean 自动装配的歧义。
  • JSR-250 Annotations:Spring 支持基于 JSR-250 注解的以下注解,@Resource、@PostConstruct 和@PreDestroy。 

6、SpringMVC 的流程? 

  1. 用户发送请求至前端控制器 DispatcherServlet;
  2. DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取Handle
  3. 处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给 DispatcherServlet;
  4. DispatcherServlet 调用 HandlerAdapter 处理器适配器;
  5. HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
  6. Handler 执行完成返回 ModelAndView;
  7. HandlerAdapter 将 Handler 执 行 结 果 ModelAndView 返 回 给DispatcherServlet ; 
  8. DispatcherServlet 将 ModelAndView 传 给ViewResolver 视图解析器进行解析;
  9. ViewResolver 解析后返回具体View;
  10. DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
  11. DispatcherServlet 响应用户。


7、Springmvc 的优点:

  1. 可以支持各种视图技术,而不仅仅局限于 JSP;
  2. 与 Spring 框架集成(如 IoC 容器、AOP 等);
  3. 清 晰 的 角 色 分 配 : 前 端 控 制 器 (dispatcherServlet) , 请 求 到处理器映射(handlerMapping), 处理器适配器(HandlerAdapter), 视图解析器(ViewResolver)。
  4. 支持各种请求资源的映射策略。 

8. Spring 通知类型使用场景分别有哪些?

  

9.IoC 控制反转设计原理?

具体设计原理如下图:


10.Spring 如何处理线程并发问题?

Spring 使用 ThreadLocal 解决线程安全问题。我们知道在一般情况下,只有无状态的 Bean 才可以在多线程环境下共享,在Spring 中,绝大部分 Bean 都可以声明为 singleton 作用域。就是因为Spring 对 一 些 Bean ( 如 RequestContextHolder 、
TransactionSynchronizationManager、LocaleContextHolder 等)中非线程安全状态采用 ThreadLocal 进行处理,让它们也成为线程安全的状态,因为有状态的 Bean 就可以在多线程中共享了。

ThreadLocal 和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而 ThreadLocal 则从另一个角度来解决多线程的并发访问。 ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。 ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。

由于 ThreadLocal 中可以持有任何类型的对象,低版本 JDK 所提供的 get()返回的是 Object 对象,需要强制类型转换。但 JDK 5.0 通过泛型很好的解决了这个问题,在一定程度地简化 ThreadLocal 的使用。概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。 

最后

欢迎大家关注我的公种浩【以Java架构赢天下】,整理了960道2019年多家公司java面试题400多页pdf文档,还有一份自己平时学习整理的Java学习笔记,共500多页,文章都会在里面更新,整理的资料也会放在里面。喜欢文章记得关注我点个赞哟,感谢支持!


我的Java学习笔记(知识点+面试整理)