常见技术问题
树图思维导图提供 常见技术问题 在线思维导图免费制作,点击“编辑”按钮,可对 常见技术问题 进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:bcc8f3d3a87225d4b346df0bbffa64aa
常见技术问题思维导图模板大纲
集合
分类
Collection
List
ArrayList
LinkedList
Set
HashSet
TreeSet
LinkedHashSet
Queue
Stack
Queue
Map
HashMap
解决hash冲突的方法
开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
再哈希法
链地址法
建立一个公共溢出区
原理
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用LinkedList来解决碰撞问题,当发生碰撞了,对象将会储存在LinkedList的下一个节点中。 HashMap在每个LinkedList节点中储存键值对对象。 当两个不同的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的LinkedList中。键对象的equals()方法用来找到键值对。
TreeMap
ConcurrentHashMap
实现原理
ArrayList扩容
add() 将元素添加到尾端
ensureCapacityInternal() 得到最小扩容量
ensureExplicitCapacity() 判断是否需要扩容
grow() 开始扩容
hugeCapacity() 比较minCapacity和MAX_ARRAY_SIZE,确定newCapacity
总结 ArrayList的初始是空的数组,第一次添加数据的时候数组初始为默认长度10。每一次添加数据的时候,得到最小扩容量,判断是否需要扩容,扩容的长度为旧容量的1.5倍,在判断新容量和最大数组长度,最后完成扩容。
HashMap扩容
应用场景
ArrayList和LinkedList
ArrayList数组实现,优于查询
LinkedList双向链表实现,优于增删
ConcurrentHashMap实现原理
线程
线程池
几种线程池的应用场景
newCachedThreadPool
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
newFixedThreadPool
固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
newScheduledThreadPool
大小无限制的线程池,支持定时和周期性的执行线程
newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
拒绝策略
AbortPolicy中止策略
这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略
DiscardPolicy丢弃策略
当有新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。
DiscardOldestPolicy弃老策略
丢弃任务队列中的头结点,通常是存活时间最长的任务,它也存在一定的数据丢失风险。
CallerRunsPolicy调用者运行策略
当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务
何时执行拒绝策略
workers 的数量达到了 corePoolSize(任务此时需要进入任务队列),任务入队成功,与此同时线程池被关闭了,而且关闭线程池并没有将这个任务出队,那么执行拒绝策略。这里说的是非常边界的问题,入队和关闭线程池并发执行
workers 的数量大于等于corePoolSize,将任务加入到任务队列,可是队列满了,任务入队失败,那么准备开启新的线程,可是线程数已经达到 maximumPoolSize,那么执行拒绝策略。
ThreadLocal
定义及原理
1.线程内部的存储类。数据存储后,只有在指定线程内可以获取数据
2.设置(set)时为当前线程实例化ThreadLocalMap,并赋值给线程的成员变量threadLocals。ThreadLocalMap是一个Entry型的数组table。设置时把当前变量(this)作为键,底层通过位运算得到存储的索引,并在相应位置存储值
3.获取(get)时根据当前变量(this)计算存储的索引,再读取相应位置的值
4.对于不同线程的同一ThreadLocal来说,他的索引值是确定的,在不同线程中访问的位置都是table[i],只不过不同线程之间的table是独立的
5.对于同一线程的不同ThreadLocal来说,共享一个table,但索引是不同的
ThreadLocal和Synchronized的异同
相同点:都是为了解决多线程中相同变量的访问冲突问题
不同点1:Synchronized是通过线程等待,牺牲时间来解决访问冲突;ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突
不同点2:ThreadLocal具有线程隔离的效果,只有在线程内能获取到对应的值
注解
元注解的定义
@Retention
1.@Retention(RetentionPolicy.SOURCE),注解仅存在于源码中 2.@Retention(RetentionPolicy.CLASS),默认,注解存在于class文件中,但运行时无法获得 3.@Retention(RetentionPolicy.RUNTIME),常用,注解存在于class文件中,且运行时可以通过反射获得
@Target
1.@Target(ElementType.TYPE),常用,作用接口、类、枚举、注解 2.@Target(ElementType.FIELD),常用,作用属性字段、枚举的常量 3.@Target(ElementType.METHOD),常用,作用方法 4.@Target(ElementType.PARAMETER),作用方法参数 5.@Target(ElementType.CONSTRUCTOR),作用构造函数 6.@Target(ElementType.LOCAL_VARIABLE),作用局部变量 7.@Target(ElementType.ANNOTATION_TYPE),作用于注解 8.@Target(ElementType.PACKAGE),作用于包 9.@Target(ElementType.TYPE_PARAMETER),作用于类型泛型
@Documented
指能够将注解中的元素包含到Javadoc中去
@Inherited
指注解修饰了父类,若其子类未被其他注解修饰,则其子类继承父类的注解
@Repeatable
指注解可以同时作用一个对象多次
内存模型
程序计数器(线程隔离)
1.程序计数器就是记录当前线程执行程序的位置,改变计数器的值来确定执行的下一条指令,比如:循环、分支、方法跳转都是依赖程序计数器来完成
2.JVM多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线程切换时能够恢复到正确的位置,每条线程的程序计数器是私有的
虚拟机栈(线程隔离)
1.创建线程的时候就会创建一个虚拟机栈,生命周期与线程相同
2.虚拟机执行程序时,每个方法会创建一个栈帧,栈帧存放在虚拟机栈中,通过压栈出栈的方式进行方法调用。栈帧又分为以下几个区域,包括:局部变量表、操作数栈、动态连接、方法出口等
3.局部变量存放在虚拟机栈的局部变量表,其中引用型的变量,只存储引用地址
4.内存溢出(OutOfMemoneyError):用户的每个请求开启一个线程,每个线程创建一个虚拟机栈。当并发量大时,可能导致内存溢出。可以适当地把虚拟机栈的大小调小,减少内存的使用量来提高系统的并发量
5.栈内存溢出(StackOverFlowError):当虚拟机栈的大小调小后,又会引发方法调用深度的问题。因为每个方法都会生成一个栈帧,如果方法调用深时会创建大量栈帧,可能导致栈内存溢出
本地方法栈(线程隔离)
1.调用本地方法(native)时,用于存储局部变量表、操作数栈等
堆(线程共享)
1.虚拟机启动时创建,堆中存放对象的实例
2.堆分为新生代(Eden、From Survivor和To Survivor)和老年代。垃圾回收主要回收的就是堆
3.Eden:新创建的对象存放在该区
4.From Survivor和To Survivor:保存新生代垃圾回收后仍存活的对象,底层使用复制算法
5.老年代:经过多次(默认15次)新生代垃圾回收仍存活的对象存放至老年代
6.内存溢出(OutOfMemoneyError):堆中分配的对象实例过多,且大部分对象都在使用,就会出现内存溢出
方法区(线程共享)
1.通常被描述为堆的逻辑部分,即永久代
2.存放已被JVM加载的类信息、常量、静态变量等数据
3.JAVA8之后没有方法区,取而代之的是元空间
垃圾回收GC
常见的垃圾判断算法
可达性分析算法:通过GC ROOT的对象作为搜索起始点,通过引用向下搜索,所走过的路径称为引用链。通过对象是否有到达引用链的路径来判断是否可被回收。可作为GC ROOT的对象:虚拟机栈中引用的对象、方法区中静态变量引用的对象、方法区中常量引用的对象等
引用计数算法:为每个对象添加一个计数器,根据是否为0判断对象是否可被回收,但无法解决循环引用问题
常见的垃圾回收算法
标记-清除算法:最基础的垃圾回收算法,先把内存区域中的可回收对象标记成垃圾,再将这些垃圾拎出来清理掉。但会带来内存碎片问题
复制算法:将可用内存划分为大小相等的两块,每次只使用其中的一块。当一块的内容用完了,就将还存活着的对象复制到另一块上,然后再把这块的内存空间一次清理掉。但会带来一半的内存浪费
标记-整理算法:先把内存区域中的可回收对象标记成垃圾,然后让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。但效率比复制算法差很多
分代收集算法:融合上述3种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳,根据对象存活周期的不同将内存划分为几块。在新生代中,每次GC都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;在老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理或标记-整理来进行回收
内存区域与回收策略
Eden区:大多数情况下,对象会在新生代Eden区中分配。当Eden区没有足够空间进行分配时,JVM会发起一次Minor GC,Eden区中绝大部分对象会被回收,而那些存活的对象,将会送到Survivor的From区
Survivor区:介于Eden区和Old区的缓冲,减少被送到老年代的对象,进而减少Major GC的发生。其分为From区和To区。每次执行Minor GC,会将Eden区中存活的对象放到From区。而在From区中,达到指定年龄(16次Minor GC)仍存活的对象会被送到老年代
Old区:大对象直接进入老年代,即需要大量连续内存空间的JAVA对象,最典型的就是很大的字符串及数组。JVM提供XX:PretenureSizeThreshold参数,令大于这个设置的对象直接在老年代分配,避免在Eden区或Survivor区发生大量的内存复制;长期存活的对象进入老年代,对象晋升老年代的年龄阈值,JVM提供XX:MaxPretenuringThreshold参数来设置
如何获取dump文件
内存:jmap -F -dump:format=b,file=test.txt 3239
线程栈:jstack -F 3239 > test.txt
类加载
类加载原理
加载、验证、准备、解析、初始化
自定义类加载器
七大原则
单一职责原则:对类来说,即一个类应该只负责一项职责。 开闭原则:对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有代码,使用接口和抽象类实现一个热插拔的效果。 里氏替换原则:任何基类可以出现的地方,子类一定可以出现。实现抽象的规范,实现子父类相互替换。 依赖倒置原则:针对接口编程,依赖于抽象而不依赖于具体。 接口隔离原则:降低耦合度,接口单独设计,相互隔离。 最少知道原则(迪米特法则):一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。 合成复用原则:尽量使用聚合、组合的方式,而不是使用继承。 开闭原则(Open-Closed Principle, OCP)是指一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则
强调的是用抽象构建框架,用实现扩展细节。 开闭原则,是面向对象设计中最基础的设计原则。它指导我们如何建立稳定灵活的系统。 例如:我们版本更新,尽可能不修改原代码,但是可以增加新功能。
常见设计模式
常用设计模式:单例、工厂、抽象工厂、适配器、代理、桥接、装饰、组合等
单例实现种类: (1)恶汉式:长时间不用,占用内存空间 懒加载:线程不安全,需要加同步锁
SpringMVC
拦截器和过滤器的执行顺序
请求->过滤器前->拦截器前->方法->拦截器后->过滤器后->响应
拦截器和过滤器的区别
1.拦截器是基于java的反射机制,而过滤器是基于函数回调
2.拦截器不依赖于servlet容器,而过滤器依赖于servlet容器
3.拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用
4.拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问
5.在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
请求流程
AOP
IOC
Bean生命周期
MyBatis
分页
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。 分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数。
工作原理
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 对结果集的解析过程。
Redis
优势
1.性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s
2.丰富的数据类型 – Redis支持String,List,Hash,Set及ZSet数据类型操作
3.原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来
4.丰富的特性 – Redis还支持publish/subscribe,通知,key过期等等特性
AOF
快照持久化
缓存异常
缓存雪崩
缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉
解决方案 1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。 2.一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。 3.给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。
缓存穿透
缓存击穿
缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库
解决方案 1.设置热点数据永远不过期。 2.加互斥锁,互斥锁
缓存预热
缓存降级
Quartz
3大组成部分
调度器(Scheduler)
任务(Job)
触发器(Trigger)
JobDetail的设计原理
每次Scheduler调度执行一个Job时,首先找到对应的Job,然后创建该Job实例,再去执行Job的execute方法,任务执行结束后,关联的Job实例会被释放,且会被JVM GC清除
采用JobDetail&Job的实现方式是因为任务有可能并发执行。如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail&Job方式,Sheduler每次执行都会根据JobDetail创建一个新的Job实例,就可以规避并发访问的问题
JobDataMap的作用
1.JobDataMap实现了JDK的Map接口,支持以Key-Value形式存储数据
2.JobDetail、Trigger都支持使用JobDataMap来设置一些信息
3.Job执行execute方法时,JobExecutionContext可以获取到JobDataMap中的信息
ES
如何实现Master选举
Elasticsearch的选主是ZenDiscovery模块负责的,主要包含Ping(节点之间通过这个RPC来发现彼此)和Unicast(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分; 对所有可以成为master的节点(node.master: true)根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点。 如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。 补充:master节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;data节点可以关闭http功能。
事务
Transactional的原理
隔离级别
多个事务读可能会道理以下问题 脏读:事务B读取事务A还没有提交的数据 不可重复读:,一行被检索两次,并且该行中的值在不同的读取之间不同时 幻读:当在事务处理过程中执行两个相同的查询,并且第二个查询返回的行集合与第一个查询不同时 这两个区别在于,不可重复读重点在一行,幻读的重点 ,返回 的集合不一样
子主题 2
传播特性
select for update作用
特性
原子性,要么执行,要么不执行 隔离性,所有操作全部执行完以前其它会话不能看到过程 一致性,事务前后,数据总额一致 持久性,一旦事务提交,对数据的改变就是永久的
索引
索引类型
普通索引、唯一索引、主键索引、全文索引
实现原理
如何查看SQL语句的执行效率
分库分表
水平拆分:把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据 垂直拆分:把一个有很多字段的表给拆分成多个表,或者是多个库上去。每个库表的结构都不一样,每个库表都包含部分字段
锁
按锁的粒度划分:可分为表级锁、行级锁、页级锁 按锁级别划分:可分为共享锁、排它锁 按加锁方式划分:可分为自动锁、显式锁 按操作划分:可分为DML锁、DDL锁 按使用方式划分:可分为乐观锁、悲观锁
悲观锁
多写少读
乐观锁
使用数据版本(version)实现
使用时间戳(timestamp)实现
多读少写