synchronized 原理,源码级
synchronized的特性
- 原子性
- 可见性
- 有序性
- 可重入性
synchronized可以修饰静态方法、成员函数,同时还可以直接定义代码块,但是归根结底它上锁的资源只有两类:一个是对象,一个是类。
volatile 作用和实现原理
volatile通常被比喻成”轻量级的synchronized”,也是Java并发编程中比较重要的一个关键字。和synchronized不同,volatile是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
volatile的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用volatile修饰就可以了。
Threadlocal原理
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
线程池的每个参数和实现原理
- corePoolSize: 核心线程数量。规定线程池有几个线程(worker)在运行
- maximumPoolSize: 最大线程数量。当workQueue满了,不能添加任务的时候,这个参数才会生效。规定线程池最多只能有多少个线程(worker)在执行
- keepAliveTime: 线程池维护线程所允许的空闲时间。超出corePoolSize大小的那些线程的生存时间,这些线程如果长时间没有执行任务并且超过了keepAliveTime设定的时间,就会消亡
- unit:生存时间对于的单位
- workQueue: 等待队列。存放任务的队列
- threadFactory: 创建线程的工厂
- handler:当workQueue已经满了,并且线程池线程数已经达到maximumPoolSize,将执行拒绝策略
springboot的自动装配
通过注解或者特定的配置,能实现自动加载一整个模块的功能,不需要开发者做太多的配置。
ioc和aop技术原理
IoC (Inverse of Control)反向控制或控制反转、依赖注入,程序之间解耦
对象创建和使用的控制权转移到了Spring容器,由Spring容器来控制。
IoC利用Java反射机制。
AOP (Aspect Oriented Programming) 面向切面编程(也叫面向方面)
AOP利用代理模式。
Struts2中的拦截器,就是使用AOP的思想。使用AOP来管理事务。
项目中怎么对jvm调优的
-XX:MetaspaceSize=128m (元空间默认大小)
-XX:MaxMetaspaceSize=128 (元空间最大大小)
-Xms1024m (堆最大大小)
-Xmx1024m (堆默认大小)
-Xmn256m (新生代大小)
-Xss256k (栈最大深度大小)
-XX:SurvivorRatio=8 (新生代分区比例 8:2)
-XX:+UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器)
-XX:+PrintGCDetails (打印详细的GC日志)
JDK8 之后把 -XX: PermSize 和 -XX:MaxPermGen 移除了,取而代之的是:
-XX:MetaspaceSize=128m (元空间默认大小)
-XX:MaxMetaspaceSize=128m (元空间最大大小)
currentHashmap原理
CAS原理
CAS:Compare and Swap,即比较再交换。
CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
数据库乐观锁和悲观锁
悲观锁(Pessimistic Locking):借助数据库锁机制在修改数据之前先锁定,再修改的方式被称之为悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)。
乐观锁( Optimistic Locking ):在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。
1、乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。
2、悲观锁依赖数据库锁,效率低。更新失败的概率比较低。
为什么索引用b+树而不用二叉树
- 二叉查找树(BST):解决了排序的基本问题,但是由于无法保证平衡,可能退化为链表
- 平衡二叉树(AVVIL): 通过旋转解决了平衡的问题,但是旋转操作效率太低
- 红黑树:通过舍弃严格的平衡和引入红黑节点,解决了AVI旋转效率过低的问题,但是在磁盘等场景下,树仍然太高,IO次数太多
- B树:通过将二叉树改为多路平衡查找树,解决了树过高的问题
- B+树:在B树的基础上,将非叶节点改造为不存储数据的纯索引节点,进一步降低了树的高度;此外将叶节点使用指针连接成链表,范围查询更加高效
Java
list set 和map的区别
List和Set是存储单列数据的集合,Map是存储键值对这样的双列数据的集合;
List中存储的数据是由顺序的,并且值运行重复;
Map中存储的数据是无序的,它的键是不允许重复的,但是值是允许重复的;
Set中存储的数据是无顺序的,并且不允许重复,但元素在集合中的位置是由元素的hashcode决定的,即位置是固定的。
List 和 Set 是实现了collection接口。Map是一个接口。
set去重原理
hash值:是一个十进制的整数,由系统随机给出,它是数据的逻辑地址值,而不是实际存储的物理地址。
Set集合去重主要是调用 add 方法时,使用了 hashCode 方法和 equals 方法:如果在 Set集合 中找不到与该元素 hashCode 值相同的元素,则说明该元素是新元素,会被添加到 Set 集合中;如果两个元素的 hashCode 值相同,并且使用 equals 方法比较后,返回值为 true,那么就说明两个元素相同,新元素不会被添加到 Set 集合中;如果 hashCode 值相同,但是 equals 方法比较后,返回值为 false ,则两个元素不相同,新元素会被添加到 Set 集合中。
synchronized锁原理
bean生命周期
在传统的Java应用中, Bean的生命周期很简单,使用Java关键字new进行Bean的实例化后,这个Bean就可以使用了。一旦这个Bean长期不被使用,Java自动进行垃圾回收。
相比之下,Spring中Bean的生命周期较复杂,大致可以分为以下5个阶段:
- Bean的实例化
- Bean属性赋值
- Bean 的初始化
- Bean的使用
- Bean的销毁
Spring 根据 Bean 的作用域来选择 Bean 的管理方式,
- 对于 singleton 作用域的 Bean 来说,Spring IoC 容器能够精确地控制 Bean 何时被创建、何时初始化完成以及何时被销毁;
- 对于 prototype 作用域的 Bean 来说,Spring IoC 容器只负责创建,然后就将 Bean 的实例交给客户端代码管理,Spring IoC 容器将不再跟踪其生命周期。
如何解决循环依赖问题
- Spring是通过递归的方式获取目标bean及其所依赖的bean的;
- Spring实例化一个bean的时候,是分两步进行的,首先实例化目标bean,然后为其注入属性。
Spring在实例化一个bean的时候,是首先递归的实例化其所依赖的所有bean,直到某个bean没有依赖其他bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性的。
Java并发
线程池参数及工作原理
项目中是如何确定线程池这几个参数的
volatile关键字作用
volatile如何保证可见性
java并发模型
MQ
mq工作原理(流程)
MQ发布订阅交换机有几种模式
RabbitMQ的五种队列模式:
- 简单模式Hello World
- 工作队列模式Work Queue
- 发布/订阅模式Publish/Subscribe
- 路由模式Routing
- 通配符模式Topic
如何保证消息的可靠性
- 发送方采取发送者确认模式
- MQ进行队列及消息的持久性
- 消费者消费成功后手动确认消息
如何保证消息不被重复消费
消息重复的原因有两个:
- 生产时消息重复
- 消费时消息重复
让每个消息携带一个全局的唯一ID,即可保证消息的幂等性,具体消费过程为:
- 消费者获取到消息后先根据id去查询redis/db是否存在该消息
- 如果不存在,则正常消费,消费完毕后写入redis/db
- 如果存在,则证明消息被消费过,直接丢弃
mq中的死信队列
死信队列:DLX (dead-letter-exchange)
利用DLX,当消息在一个队列中变成死信(dead message)之后,它能被重新publish到另一个Exchange,这个Exchange就是DLX
消息变成死信有以下几种情况:
- 消息被拒绝(basic.reject / basic.nack),并且requeue = false
- 消息TTL过期
- 队列达到最大长度
死信处理过程:
- DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。
- 当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上去,进而被路由到另一个队列。
- 可以监听这个队列中的消息做相应的处理。
Redis
为什么用redis,解决什么问题
Redis的优点
1.关系型数据库是运行在磁盘,读写需要用到I/O操作,而Redis运行在内存中。读取速度快。官方号称支持并发11瓦特读操作,并发8瓦特写操作,可以说是相当彪悍了
2.虽然读取在内存中,但是也提供了持久化操作,即可以将内存中的数据异步写入到硬盘中,同时不影响继续提供服务。
3,支持数据结构丰富(string(字符串),list(链表),set(集合),zset(sorted set - 有序集合))和Hash(哈希类型,md5加密出来的那个串)
- 性能
服务器读取数据库耗时长,而直接读取Redis缓存显然是更快。
- 并发
多个请求同时访问数据库,数据库承受不了那么大的压力,数据库会出现连接异常,影响用户体验。
这个时候,就需要使用的的Redis的做一个缓冲操作,让请求先访问到的Redis的,而不是直接访问数据库。
什么是缓存击穿?
缓存击穿就是多个请求访问的数据redis内存中没有,就会直接访问到mysql数据库,(数据库没有这个数据,所以就不会存到redis缓存中,所以才会在redis缓存中查不到),而数据库处理不了这么多请求,这个时候redis就没有起到作用而是直接被穿透打到mysql数据库了,这就是缓存击穿。
解决方法:
可以加判断,当查询redis缓存中没有转发到mysql数据库时,数据库没有该数据就把查询的数据存到redis缓存中,值为null,并设置一个缓存的失效时间,这时在缓存失效之前,所有通过此key的访问都被缓存挡住了。后面如果此key对应的数据在DB中存在时,缓存失效之后,通过此key再去访问数据,就能拿到新的value了。
什么是缓存雪崩?
缓存雪崩是指,我们的缓存设置的过期时间相同,导致大面积的缓存过期。请求都转发到了DB(mysql)数据库中。DB压力过大雪崩。
解决方法:
将系统中key的缓存失效时间均匀地错开,防止统一时间点有大量的key对应的缓存失效。比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
redis是单线程为什么快
一方面,Redis的操作都在内存中完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。
另一方面,Redis使用了多路复用机制,使其在网络的IO的时候能够并发处理大量的客户端请求,实现高吞吐率。
1.redis是基于内存的,内存的读写速度非常快;
2.redis是单线程的,省去了很多上下文切换线程的时间;
3.redis使用多路复用技术,可以处理并发的连接;
介绍cluster集群方式
Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的内容。
任何两个节点之间都是相互连通的。客户端可以与任何一个节点相连接,然后就可以访问集群中的任何一个节点。对其进行存取和其他操作。
项目中redis的集群架构是哪种
Redis支持三种集群方案:
- 主从复制模式
- Sentinel(哨兵)模式
- Cluster模式(集群)
cluster集群和主从加哨兵的区别
Redis 主从模式虽然能做到很好的数据备份,但是他并不是高可用的。一旦主服务器点宕机后,只能通过人工去切换主服务器。因此 Redis 的哨兵模式也就是为了解决主从模式的高可用方案。
哨兵模式引入了一个 Sentinel 系统去监视主服务器及其所属的所有从服务器。一旦发现有主服务器宕机后,会自动选举其中的一个从服务器升级为新主服务器以达到故障转义的目的。
同样的 Sentinel 系统也需要达到高可用,所以一般也是集群,互相之间也会监控。而 Sentinel 其实本身也是一个以特殊模式允许 Redis 服务器。
[
](https://blog.csdn.net/m0_37900506/article/details/118872819)
JVM
JVM类加载过程
类的整个生命周期包括加载、验证、准备、解析、初始化、使用和卸载7个阶段,其中验证、准备、解析这3个部分统称为连接。
简单介绍JVM内存区域
- 程序计数器(PC计数器:Program Counter Register)
PC计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码行号指示器。
- Java栈(虚拟机栈)
线程私有内存空间,它的生命周期和线程相同。
- 本地方法栈
本地方法栈和Java虚拟机栈发挥的作用相似,主要区别是Java虚拟机栈执行的是Java方法服务,而本地方法栈执行Native方法服务。
- 堆
Java堆是被所有线程共享的最大的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
在Java中,堆被分成两个不同的区域:新生代(Young Generation)、老年代(Old Generation)。
- 方法区
方法区和Java堆一样,为多个线程共享,它用于存储类信息、常量、静态常量和即时编译后的代码等数据。
垃圾回收算法有哪些
- 标记清除算法
- 标记复制算法
- 标记整理算法
- 分代收集算法
CMS和G1的区别
- 使用范围不一样
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用
G1收集器收集范围是老年代和新生代。不需要结合其他收集器使用
2. STW的时间
CMS收集器以最小的停顿时间为目标的收集器。
G1收集器可预测垃圾回收的停顿时间(建立可预测的停顿时间模型)
- 垃圾碎片
CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片
收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
- 垃圾回收的过程不一样
什么时候会进行Full GC
- System.gc()方法被调用
- 老年代空间不足
- 永生区空间不足
- CMS GC是出现promotion failed和concurrent mode failure
- HandlePromotionFailure
- 堆中分配很大的对象
CMS垃圾回收有哪些阶段
- 初始标记阶段
- 并发标记阶段
- 重新标记阶段
- 并发整理阶段
数据库
索引数据结构
数据库索引,是数据库管理系统中一个排序的数据结构,主要有B树索引、Hash索引两种。
B-树:https://mp.weixin.qq.com/s/rDCEFzoKHIjyHfI_bsz5Rw
B+树:https://mp.weixin.qq.com/s/jRZMMONW3QP43dsDKIV9VQ
补充:
什么情况下会使用索引呢?
- 主键自动建立唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其他表关联的字段,外键关系建立索引
- 频繁更新的字段不适合建立索引,因为每次更新不单单时更新了记录还会更新索引
- where 条件里用不到的字段不创建索引
- 查询中排序的字段,排序的字段若通过索引去访问将会大大提高排序速度
- 查询中统计或者分组的字段
哪些情况不需要创建索引
- 表记录太少
- 经常增删改的表
- 如果某个数据列包含许多重复的内容,为它建立索引就没有太多太大实际效果
MySQL存储引擎有哪些?有什么区别?
存储引擎是数据库管理系统用来从数据库创建、读取和更新数据的软件模块。
MySQL中有两种类型的存储引擎:事务性和非事务性。
对于MySQL 5.5及更高版本,默认的存储引擎是InnoDB。在5.5版本之前,MySQL的默认存储引擎是MyISAM。
MySQL的存储引擎:
- InnoDB
这是MySQL 5.5或更高版本的默认存储引擎。它提供了事务安全(ACID兼容)表,支持外键引用完整性约束。它支持提交、回滚和紧急恢复功能来保护数据。它还支持行级锁定。当在多用户环境中使用时,它的“一致非锁定读取”提高了性能。它将数据存储在集群索引中,从而减少了基于主键的查询的I/O。
- MyISAM
该存储引擎管理非事务性表,提供高速存储和检索,支持全文搜索。
- MEMORY
提供内存中的表,以前称为堆。它在RAM中处理所有数据,以便比在磁盘上存储数据更快地访问。用于快速查找引用和其他相同的数据。
- MERGE
将多个类似的MyISAM表分组为一个表,可以处理非事务性表,默认情况下包括这些表。
- EXAMPLE
你可以使用此引擎创建表,但不能存储或获取数据。这样做的目的是教开发人员如何编写新的存储引擎。
- ARCHIVE
用于存储大量数据,不支持索引。
- CSV
在文本文件中以逗号分隔值格式存储数据。
- BLACKHOLE
受要存储的数据,但始终返回空。
- FEDERATED
将数据存储在远程数据库中。
什么是覆盖索引
覆盖索引是select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖。
如何实现索引覆盖?
最常见的方法就是:将被查询的字段,建立到联合索引(如果只有一个字段,普通索引也可以)里去。
哪些写法会导致索引失效
单个索引
- 使用!=或者<>导致索引失效
- 类型不一致导致的索引失效
- 函数导致的索引失效
- 运算符导致的索引失效
- OR引起的索引失效
- 模糊搜索导致的索引失效
- NOT IN、NOT EXISTS导致索引失效
这两种用法,也将使索引失效。但是NOT IN 还是走索引的,千万不要误解为 IN 全部是不走索引的。
- IS NULL 不走索引,IS NOT NULL 走索引
根据这个情况,建议大家这设计字段的时候,如果没有必要的要求必须为NULL,那么最好给个默认值空字符串,这可以解决很多后续的麻烦(有深刻的体验<体验=教训>。
复合索引
- 最左匹配原则
- 如果使用了!=会导致后面的索引全部失效
事务隔离级别有哪些
事务的四大特性分别是:原子性、一致性、隔离性、持久性
脏读(Dirty Read):一个事务读到了另一个未提交事务修改过的数据。(脏读只在读未提交隔离级别才会出现。)
不可重复读(Non-Repeatable Read):一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值。(不可重复读在读未提交和读已提交隔离级别都可能会出现。)
幻读(Phantom):一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。(幻读在读未提交、读已提交、可重复读隔离级别都可能会出现。)
MySQL的四种隔离级别:
- Read uncommitted (读未提交)
解决了更新丢失,但还是可能会出现脏读
- Read committed (读提交)
解决了更新丢失和脏读问题
- Repeatable read (可重复读取)
解决了更新丢失、脏读、不可重复读,但是还会出现幻读
- Serializable (可序化)
解决了更新丢失、脏读、不可重复读、幻读(虚读)
MySQL的隔离级别的作用就是让事务之间相互隔离,互不影响,这样可以保证事务的一致性。
在MYSQL数据库中,支持上面四种隔离级别,默认的为Repeatable read(可重复读);而在Oracle数据库中,只支持Serializeble(串行化)级别和Read committed(读已提交)这两种级别,其中默认的为Read committed级别。
设置隔离级别
- 通过set命令
SET [ GLOBAL | SESSION ] TRANSACTION ISOLATION LEVEL level;
其中level有4种值:
level: {
REPEATABLE READ
| READ COMMITTED
| READ UNCOMMITTED
| SERIALIZABLE
}
- 通过服务启动项命令
可以修改启动参数transaction-isolation的值
比如 —transaction-isolation=READ UNCOMMITTED,那么事务的默认隔离级别就从原来的REPEATABLE READ变成了READ UNCOMMITTED。
Spring Cloud
网关作用
网关: 是一个网络整体系统中的前置门户入口。请求首先通过网关,进行路径的路由,定位到具体的服务节点上。
可以将服务跟外网进行隔离起到一定的保护作用,同时服务间局域网通信更加快捷。 而且在网关中可以做限流、权限校验,使得服务更加专注自身业务。
网关有以下几个作用:
- 统一入口:为全部微服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
- 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
- 动态路由:动态的将请求路由到不同的后端集群中。
- 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。
Eureka底层原理
服务启动后向Eureka注册,Eureka Server会将注册信息向其他Eureka Server进行同步,当服务消费者要调用服务提供者,则向服务注册中心获取服务提供者地址,然后会将服务提供者地址缓存在本地,下次再调用时,则直接从本地缓存中取,完成一次调用。
当服务注册中心Eureka Server检测到服务提供者因为宕机、网络原因不可用时,则在服务注册中心将服务置为DOWN状态,并把当前服务提供者状态向订阅者发布,订阅过的服务消费者更新本地缓存。
服务提供者在启动后,周期性(默认30秒)向Eureka Server发送心跳,以证明当前服务是可用状态。Eureka Server在一定的时间(默认90秒)未收到客户端的心跳,则认为服务宕机,注销该实例。
Feign远程调用原理
Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。
Zookeeper,Nacos,Eureka原理和区别
相同点:都可以实现分布式注册中心框架
不同点:
Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)
Zookeeper: 采用CP保证数据的一致性的问题,原理是采用ZAB原子广播协议。
当我们ZK领导者宕机或出现了故障,会自动重新实现选举新的领导角色,整个选举的过程中为了保证数据一致性的问题,整个微服务无法实现通讯(本地有缓存除外)。
还有可运行的节点必须满足过半机制,整个zk才可以使用,要不然会奔溃。
Eureka: 采用AP设计理念架构注册中心,相互注册(你中有我,我中有你),完全去中心化,也就是没有主从之分,只要有一台Eureka节点存在整个微服务就可以实现通讯。
Nacos: 从1.0版本选择AP和CP混合形式实现注册中心,默认情况下采用AP保证服务可用性,CP形式底层采用Raft协议保证数据的一致性问题。
如果选择为AP模式,注册服务的实例仅支持临时模式,在网络分区的的情况允许注册服务实例。
选择CP模式可以支持注册服务的实例为持久模式,在网络分区的产生了抖动情况下不允许注册服务实例。
ZK的设计原则是CP,即强一致性和分区容错性。他保证数据的强一致性,但舍弃了可用性,如果出现网络问题可能会影响ZK的选举,导致ZK注册中心的不可用。
Eureka的设计原则是AP,即可用性和分区容错性。他保证了注册中心的可用性,但舍弃了数据一致性,各节点上的数据有可能是不一致的(会最终一致)。
Eureka采用纯Java实现,除实现了注册中心基本的服务注册和发现之外,极大的满足注册中心的可用性,即使只有一台服务可用,也可以保证注册中心的可用性。
项目中spring cloud用的组件
- Eureka - 服务的注册与发现
Eureka在SpringCloud中作为一个服务的注册与发现的关键组件,是以一个单独的项目的形式,作为注册中心。
- Feign + Ribbon - 服务的调用和负载均衡
Feign是一个单独的组件,是一种声明式、模块化的HTTP客户端,它实现服务之间的相互调用,并隐藏调用细节。
- Hystrix - 服务的降级,熔断
快速失败策略,避免服务雪崩;
舱壁模式,实现依赖隔离;
提供熔断器机制,即熔断策略
- config - 分布式配置管理
将服务的配置文件统一管理,实时更新
- zuul - 网关
核心是过滤器
- Sleuth - 服务追踪
问题
分布式事务
session如何处理
介绍一下spring的两大特性
什么是面向切面?介绍一下切面和切点
项目组中是如何调优的?
JVM出现问题如何发现,解决
项目中用到redis哪些功能?解决了哪些问题?
有没有做集群?
SpringCloud用到哪些组件?
介绍一下Feigh
网关用的是哪个?
熔断用了没有?
项目中用到的持久化框架是哪个?
数据库索引有哪些?
垃圾回收如何实现?
线程池原理