Java面试圣经
基础篇
基本功
- 面向对象的特征
- final, finally, finalize 的区别
- 重载和重写的区别
- 说说反射的用途及实现
- equals 与 == 的区别
数据结构
集合
- List 和 Set 区别
- List 和 Map 区别
- Arraylist 与 LinkedList 区别
- ArrayList 与 Vector 区别
- HashMap、HashTable、ConcurrentHashMap共同点与区别
- HashMap和HashTable 都是底层数组+链表实现,ConcurrentHashMap底层结构是散列表(数组+链表)+红黑树
- HashMap可以存储null键和null值,HasTable和ConcurrentHashMap的key和value都不能为null
- HasMap线程不安全,HashTable和ConcurrentHashMap是线程安全的。HashTable实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,而ConcurrentHashMap作为一个高并发的容器,它是通过部分锁定+CAS算法来进行实现线程安全的。CAS算法也可以认为是乐观锁的一种
- HashSet 和 HashMap 区别
- HashMap 的工作原理及代码实现
- ConcurrentHashMap 的工作原理及代码实现
- HashMap是如何扩容的
- HashMap如何避免key碰撞
- HashMap死循环问题
进阶篇
网络
- 讲讲TCP/IP
- 讲讲TCP、UDP、IP
- TCP三次握手、四次握手
IO
Java中IO流的分类
NIO、BIO、AIO
See:关于BIO和NIO的理解
- BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- AIO(NIO 2.0):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的IO请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
线程
References Java线程模型
说说 CountDownLatch 原理
CountDownLatch是同步工具类之一,可以指定一个计数值,在并发环境下由线程进行减1操作,当计数值为0之后,被await方法阻塞的线程将会唤醒,实现线程间的同步。
说说 CyclicBarrier 原理
CyclicBarrier 字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时候,屏障才会开门。所有被屏障拦截的线程才会运行。
CyclicBarrier是由ReentrantLock可重入锁和Condition共同实现的。
说说 Semaphore 原理
Semaphore也叫信号量, 在JDK1.5被引入, 可以用来控制同时访问特定资源的线程数量, 通过协调各个线程, 以保证合理的使用资源.
Semaphore内部维护了一组虚拟的许可, 许可的数量可以通过构造函数的参数指定.
- 访问特定资源前, 必须使用acquire方法获得许可, 如果许可数量为0, 该线程则一直阻塞, 直到有可用的许可
- 访问资源后, 使用release释放许可
Semaphore和ReentrantLock类似,获取许可有公平策略和非公平许可策略,默认情况下使用非公平策略
应用场景 :
Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。
假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制说说 Exchanger 原理
说说 CountDownLatch 与 CyclicBarrier 区别
CountDownLatch | CyclicBarrier |
---|---|
减计数方式 | 加计数方式 |
计算为0时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
计数为0时,无法重置 | 计数达到指定值时,计数置为0重新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复使用 | 可重复利用 |
CountDownLatch 强调的是一个线程(或多个)需要等待另外的n个线程干完某件事情之后才能继续执行。
举个例子:有五个人,一个裁判。这五个人同时跑,裁判开始计时,五个人都到终点了,裁判喊停,然后统计这五个人从开始跑到最后一个撞线用了多长时间。
我们实现代码的思路可能是这样:main线程是裁判,5个Worker是跑步的,运动员先准备,裁判喊跑,运动员才开始跑(这是第一次同步,对应begin)。5个人谁跑到终点了,countdown一下,直到5个人全部到达,裁判喊停(这是第二次同步,对应end),然后算时间。
CyclicBarrier强调的是n个线程,大家相互等待,只要有一个没完成,所有人都得等着。
ThreadLocal 原理分析
用于防止对可变的单实例变量或全局变量进行共享
See ThreadLocal源码解读
讲讲线程池的实现原理
See 深入分析java线程池的实现原理
线程池的几种方式与使用场景
锁机制
- AQS详解
AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。
See Java并发之AQS详解
说说线程安全问题
某个属性是被多线程共享的资源,同时多线程有读写操作,就有可能(注意是有可能)存在线程安全问题。
即使是有多线程对同一个共享资源都有读写,也不能笼统的说就一定存在线程安全问题
要考虑线程安全问题并不代表一定就有线程安全问题。仿佛有点矛盾。判断存不存在线程安全问题,还要根据业务特点和发生问题导致的结果来判断。
volatile 实现原理
如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。这个就是所谓的“可见性”,就是一个线程修改了,其他线程能知道这个操作,这就是可见性。如何实现的呢?volatile修饰的变量在生成汇编代码的时候,会产生一条lock指令,lock前缀的指令在多核处理器下会引发两件事情:
- 将当前处理器缓存行的数据写回到系统内存;
- 这个写回内存的操作会使得在其它cpu里缓存了该内存地址的数据无效。
synchronized 实现原理
synchronized是用java的monitor机制来实现的,就是synchronized代码块或者方法进入及退出的时候会生成monitorenter跟monitorexit两条命令。线程执行到monitorenter时会尝试获取对象所对应的monitor所有权,即尝试获取的对象的锁;monitorexit即为释放锁。
CAS 乐观锁
乐观锁的业务场景及实现方式
ABA 问题
核心篇
数据存储
说说 SQL 优化之道
负向条件(where != 条件)不能使用索引,可以优化为
in
查询;Like 模糊查询左匹配不能使用索引, 只有右匹配能使用索引;
数据区分度不大的字段不宜使用索引,如:性别只有男,女,每次过滤掉的数据很少,不宜使用索引;
在属性上进行计算不能命中索引;
-
如何避免死锁:
- 以固定的顺序访问表和行;
- 大事务拆小。大事务更倾向于死锁,如果业务允许,将大事务拆小;
- 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率;
- 降低隔离级别。如果业务允许,将隔离级别调低也是较好的选择,比如将隔离级别从RR调整为RC,可以避免掉很多因为gap锁造成的死锁;
- 为表添加合理的索引。可以看到如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大。
数据库索引的原理
BTREE与HASH索引的区别, 为什么要用 BTREE索引
Hash 索引只能够用于使用 = 或者 <=> 运算符的相等比较(但是速度更快)。Hash 索引不能够用于诸如 < 等用于查找一个范围值的比较运算符;
B-tree 索引可以用于使用 =, >, >=, <, <= 或者 BETWEEN 运算符的列比较。如果 LIKE 的参数是一个没有以通配符起始的常量字符串的话也可以使用这种索引。
聚集索引与非聚集索引的区别
聚集索引:数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能有一个聚集索引。Mysql中聚集索引就是主键索引,如果没有主键,系统会自动创建一个隐含列作为表的聚集索引。
非聚集索引:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以有多个非聚集索引。
See 聚集索引与非聚集索引的总结
limit 20000 加载很慢怎么解决
选择合适的数据存储方案
聊聊 MongoDB 使用场景
聊聊 ElasticSearch 使用场景
-
不是由记录来确定属性值,而是由属性值来确定记录的位置
事务隔离级别
- 未提交读(Read uncommitted)
- 读的都是最新版本的数据, 会出现脏读
- 已提交读(Read committed [RC])
- 只能读取到已经提交的数据, 会出现不可重复读
- 可重复读(Repeatable read [RR]):在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别
- 解决了不可重复的问题, InnoDB解决了幻读问题
- 可串行化(Serializable)
- 用加锁的方式实现串行化
我们较常用的是RC和RR
- 未提交读(Read uncommitted)
MySQL的事务隔离级别是如何实现的
每行数据其实在数据库都是多个版本的,可能同一时间有很多事务在更新一条数据,事务在开始的时候会申请一个id,这个id是严格随着时间递增的,先开始的事务id总是小的,数据库的版本就是事务id的版本。数据库范式
- 第一范式(1NF): 强调每一列都是不可分割的原子数据项
- 第二范式(2NF): 在1NF的基础上,非属性码的属性必须完全依赖于主码。(在1NF基础上消除非属性码的属性对主码的部分函数依赖)
- 第三范式(3NF): 在2NF基础上,消除传递依赖
References
缓存使用
Redis 有哪些类型
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M | — |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的API | 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 |
Set(集合) | 哈希表实现,元素不重复 | 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 | 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 | 1、排行榜 2、带权重的消息队列 |
Redis 内部结构
-
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
allkeys-random:从数据集中任意选择数据淘汰
no-enviction:当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错
聊聊 Redis 使用场景
Redis 持久化机制
RDB: 这是Redis默认的持久化方式,按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件;
AOF: Redis会将每一个收到的写命令都通过Write函数追加到文件最后,类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
Redis 集群方案与实现
缓存崩溃
缓存降级
使用缓存的合理性问题
消息队列
- 消息队列的使用场景
- 消息的重发补偿解决思路
- 消息的堆积解决思路
- 自己如何实现消息队列
- 如何保证消息的有序性
- Kafka为什么快
- Kafka是如何实现几十万的高并发写入
框架篇
Spring
BeanFactory 和 ApplicationContext 有什么区别
Spring IOC 如何实现
说说 Spring AOP
Spring AOP 实现原理
动态代理(cglib 与 JDK)
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
Spring 事务实现方式
Spring 事务底层原理
Spring 其他产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)
Netty
- 为什么选择 Netty
- 说说业务中,Netty 的使用场景
- 原生的 NIO 在 JDK 1.7 版本存在 epoll bug
- 什么是TCP 粘包/拆包
- TCP粘包/拆包的解决办法
- Netty 线程模型
- 说说 Netty 的零拷贝
- Netty 内部执行流程
- Netty 重连实现
微服务篇
微服务
微服务有哪些框架
如何解决跨域
你怎么理解 RPC 框架
说说 RPC 的实现原理
说说 Dubbo 的实现原理
你怎么理解 RESTful
如何理解 RESTful API 的幂等性
如何保证接口的幂等性
说说 CAP 定理、 BASE 理论
CAP的结论非常简单:在分布式系统里,有3个属性非常重要,但只能同时满足其中的2个
- Consistency:all nodes在任何时刻看到的data都是一样的(或说client的read操作总是返回最新写入的那个value)
- Availability:系统时刻都允许操作,并且操作总会快速被Coordinator响应,最终client很快就得到返回的结果
- Partition-tolerance:尽管网路有时候会因为故障导致被分隔开,但是系统依然在正常工作(或者说在满足前述的条件下工作)
CAP权衡
如今的云计算环境里,因为网络随时都会被隔离开来,这是无法避免的,P是必须满足的,那么CAP暗示一个system要在C和A中做出抉择。
比如,Cassandra就选择了AP,对于C只能保证 Eventual Consistency(弱一致性);传统的RDBMS在一个partition里保证可用性。
怎么考虑数据一致性问题
说说最终一致性的实现方案
微服务如何进行数据库管理
如何应对微服务的链式调用异常
对于快速追踪与定位问题
分布式
谈谈业务中使用分布式的场景
Session 分布式方案
分布式锁的场景
分布式锁的实现方案
- 基于数据库实现排他锁
- 基于redis实现
- 基于zookeeper实现
分布式事务
集群与负载均衡的算法与实现
说说分库与分表设计
分库与分表带来的分布式困境与应对之策
高级进阶
算法
排序算法
See http://data.biancheng.net/sort/
JVM
References JVM8中内存基本操作
References
性能优化
- 性能指标有哪些
- 如何发现性能瓶颈
- 性能调优的常见手段
- 说说你在项目中如何进行性能调优
面试官拷问
- 平时碰到系统CPU飙高和频繁GC,你会怎么排查?