凡哥赞助 (去重)

238 阅读14分钟

Java创建对象的方式有哪几种

1.使用new关键字,最常用的 2.使用反射的机制 3.使用clone方法 4.使用反序列化创建对象

SpringCloud阿里巴巴

18年阿里巴巴提供的开源的框架,原先的springcloud提供的服务都闭源了,提供了方便

只需要添加一些注解和少量的配置就可以将springcloud应用接入阿里微服务,通过阿里中间件迅速搭建分布式应用系统

怎么处理Java异常

try负责监控可能出现异常的代码

catch 负责捕获可能出现的异常,并进行处理

finally 负责清理各种资源,不管是否出现异常都会执行

String、StringBuilder和StringBuffer三者使用的总结

操作少量的数据:使用String

多线程操作字符串缓冲区在大量数据:使用 StringBuilder

单线程操作字符串缓冲区在大量数据: :适用StringBuffer

Arraylist和Linkedlist有什么区别

Arraylist是基于数组的,在查询效率比较高,插入删除效率比较低

Linkedlist是基于链表的,插入删除效率比较高,查询效率比较低

对于添加和删除的时候,linkedlist优于arraylist,因为arraylist在做数据的添加和删除的时候需要有数据的位置的移动

当需要对数据进行随机访问的时候,选用Arraylist

当需要对数据进行多次增加删除修改时需要Linkedlist

LinkedList 一般情况下占用空间更大,因为每个节点要维护指向前后地址的两个节点

我们常说的数组是定死的数组,arraylist是一个动态数组

一般情况下,LinkedList的占用空间更大,因为每个节点要维护指向前后地址的两个节点,但也不是绝对,如果刚好数据量超过ArrayList默认的临时值时,ArrayList占用的空间也是不小的,因为扩容的原因会浪费将近原来数组一半的容量

HashMap

HashMap一般使用什么类型的元素作为Key

选择Integer,String这种不可变的类型 ,

  1. 这些类已经很规范的覆写了hashCode()以及equals()方法
  2. 像对String的一切操作都是新建一个String对象对新的对象进行拼接分割等,这些`类是Immutable(不可变)的作为不可变类天生是线程安全的
  3. 很好的优化比如可以缓存hash值,避免重复计算等等

hashmap什么情况下会产生死循环

比如123 扩容后变成321 刚开始1->2 后面变成2->1->2就造成了死循环

只会发生在jdk1.7时. 头插法 + 链表 + 多线程并发 + 扩容,累加到一起形成了 HashMap 的死循环

解决方案:多线程下建议使用 ConcurrentHashMap 替代

HashMap底层原理?

HashMap基于哈希表的Map接口实现 ,是以key-value存储形式存在 ,存放键值对

HashMap实现不是同步的,线程不安全, key、value 可以为空

Jdk1.7是基于数据加链表实现,jdk1.8是数据加链表,链表长度大于8并且数组长度大于64转红黑树

发生hash碰撞时,java 1.7 会在链表的头部插入,头插法,而java 1.8会在链表的尾部插入,尾插法

HashMap 的 table 的容量如何确定?loadFactor 是什么?该容量如何变化?这种变化会带来什么问题

table 数组大小是由 capacity 这个参数确定的,默认是16,也可以构造时传入,最大限制是1<<30;

loadFactor 是装载因子,主要目的是用来确认table 数组是否需要动态扩展,默认值是0.75,比如 table 数组大小为 16,装载因子为 0.75 时,threshold 就是12,当 table 的实际大小超过 12 时, table就需要动态扩容;

扩容时,调用 resize() 方法,将 table 长度变为原来的两倍(注意是 table 长度,而不是 threshold)

如果数据很大的情况下,扩展时将会带来性能的损失,在性能要求很高的地方,这种损失很可能很致命。

说⼀下HashMap的Put⽅法

1.根据key通过hash算法计算出数组下标

2.如果数组下标为空,就将key和value封装成一个entry对象 (JDK1.7中是Entry对象,JDK1.8中是 Node对象)并放⼊该位置

3.如果不为空 判断1.7和1.8版本

a. .如果是JDK1.7,则先判断是否需要扩容,如果要扩容就进⾏扩容,如果不⽤扩容就⽣成Entry对 象,并使⽤头插法添加到当前位置的链表中

b. 如果是JDK1.8,则会先判断当前位置上的Node的类型,看是红⿊树Node,还是链表Node

i. 如果是红⿊树Node,则将key和value封装为⼀个红⿊树节点并添加到红⿊树中去,在这个过 程中会判断红⿊树中是否存在当前key,如果存在则更新value

ii. 如果此位置上的Node对象是链表节点,则将key和value封装为⼀个链表Node并通过尾插法插 ⼊到链表的最后位置去,因为是尾插法,所以需要遍历链表,在遍历链表的过程中会判断是否存在当前key,如果存在则更新value,当遍历完链表后,将新链表Node插⼊到链表中,插⼊ 到链表后,会看当前链表的节点个数,如果⼤于等于8,那么则会将该链表转成红⿊树

iii. 将key和value封装为Node插⼊到链表或红⿊树中后,再判断是否需要进⾏扩容,如果需要就 扩容,如果不需要就结束PUT⽅法

Hash头插法

jdk1.7 新添加的元素,要插入到原本链表位置的最前面

hash冲突

在hash表中存数据时,用hash函数计算该数据要存放的地址,但是地址中已经有值存在,就产生hash冲突

一般用什么作为HashMap的key

一般用Integer、String这种不可变类当HashMap当key ,因为key是不可变的,创建字符串时,hashcode会被缓存下来,不需要再次计算,相对于其他对象更快,不用再重写 hashCode()以及equals()方法

解决hash冲突的办法有哪些

1.开放地址法:如果当前地址被占用了,就放到下一个位置,直到找到空位置

2.在哈希:再次计算地址,只道无冲突为止

3.链地址法:在同一个节点上有一个next指针,多个节点可以用next指针构成一个单向链表

4.建立公共溢出区 将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区

何时发生哈希碰撞和什么是哈希碰撞,如何解决哈希碰撞

只要两个元素的key计算的hash码值相同就会发生hash碰撞,jdk8之前使用链表解决哈希碰撞,jdk8之后使用链表+红黑树解决哈希碰撞

为什么要用spring

低侵入式设计,AOP和IOC ,对主流的框架提供了集成的支持

AOP

aop是面向切面编程,用于将一些与业务无关的,但却对多个对象产生影响的公共行为和逻辑,抽取出来封装成一个模块,而这个模块就叫做切面。优点:减少系统中的重复代码,降低了模块间的耦合度,提高了系统的可维护性。主要用权限认证、日志、事务处理

aop的实现关键主要在于代理模式,分为JDK动态代理和CGLIB动态代理

JDK动态代理和CGLIB动态代理对比

  • JDK动态代理值提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler通过的是invoke()方法反射来代用目标类中的代码的。动态将逻辑和业务绑定在一起 。Proxy是利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象
  • 如果代理类中没实现InvocationHandler接口,那么Aop会选择使用CGLIB来动态代理目标类。CGLIB是一个代码生成的类库,可以在运行时动态生成指定类的一个子类对象,并且覆盖其中特定的方法来实现的aop。CGLIB是通过继承的方式做的动态代理。所以如果某各类被 标记为final,那么他是无法使用CGLIB代理的

aop中切面、切点、连接点、通知之间的关系

1、切面:就是将公共行为封装成模块之后的类

2、切点:描述何时执行通知的规则

3、连接点:被切点匹配执行通知的位置

4、通知:是程序公共行为执行的代码操作

IOC

  • IOC是控制反转,指的是创建对象的控制权利的转移,以前创建对象的主动权是自己,而现在 是将主动权交给了spring去管理,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,有利用功能的复用。DI依赖注入,和IOC是同一个概念的不同角度的描述,一个程序在运行时依赖IOC容器来动态注入对象 需要的外部资源, 主要提供了 BeanFactory 和 ApplicationContext 两种 IOC 容器
  • IOC就是 创建对象的时候不用在去new了,可以通过spring自动生产,使用java的反射机制,根据配置文件运行时动态去创建和管理,之后再去调用
  • spring的IOC是由三种注入方式:构造器注入、set方法注入、根据注解注入

Spring事务如何使用

  • @Transactional(踹赛课肾脑) 这个注解可以添加到类上面,也可以添加到方法上面
  • 如果这个注解添加到类上面,这个类里面所有的方法都添加了事务
  • 如果把这个注解添加到方法上面,那么只为这个方法添加事务

Spring事务的机制(未完成)

spring事务的底层是基于AOP机制和数据库事务,在代码中方法要加上 @Transactional注解 ,在调用该方法时事务会生效

对象创建(实例化,填充属性,为属性赋值,初始化,销毁)

1.通过构造方法或者工厂方法实例化bean对象

2.设置对象属性(依赖注入)

3.注入Aware依赖开始执行方法设置参数

4.对象创建成功

对象销毁

1.执行 DisposableBean接口的destory() 方法

2.执行自定义的销毁方法

单例模式

饿汉式

程序启动就去创建对象,节省空间,提高执行效率

懒汉式

懒加载机制,只有在用到的时候才会创建对象,通常用双重校验锁atile 来保证线程安全的

双重校验锁为什么两次校验

第一次校验只是为了验证是否存在对象,不是判断是否创建

第二次校验是为了验证是否创建对象,加上synchronized锁保证对象不会被多次创建

volatile有什么作用?

1.防止指令重排序

volatile会在变量写操作的前后加入两个内存屏障,来保证前面的写指令和后面的读指令是有序的

2.保证各个线程之间的可见性

当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果,在主内存中读取

SpringCloud有哪些组件

  • Eureka 注册中心, ⽤来进⾏服务的⾃动注册和发现
  • ribbon:负载均衡策略, ⽤来在消费者调⽤服务时进⾏负载均衡
  • hystrix:熔断器(断路器) , 负责服务容错
  • zuul:网关, 可以进⾏服务路由、服务降级、负载均衡等
  • config:配置中心 实现服务统一管理
  • feign:服务调用 使用了动态代理的方式,实现了服务调用

hystrix的底层原理

hystrix可以完成隔离、限流、熔断、降级这些常用保护功能 hystrix的隔离分为[线程池隔离和信号量隔离

信号量隔离

信号量就是一个计数器, 当计数器计算达到设定的阈值,直接就做异常处理

线程池隔离

采用额外的线程对原来的web容器线程做管理控制,如果一个线程超时未返回,则熔断,涉及到线程池管理,线程的上下文切换,线程池的隔离成本最高

Zookeeper保证CP 一致性和分区容错性

注重数据的一致性 若主机挂掉则zk集群整体不对外提供服务了,需要选一个新主机的出来(120s作用)才能继续对外提供服务,不保证高可用

Eureka保证AP 可用性和分区容错性

注重服务的可用性,即使所有机器都挂了,也能拿到本地缓存的数据,保证高可用。

对于注册中心,ZooKeeper、Eureka 哪个更合适

eureka更适合和做注册中心,现实生活中一般用zookeeper,因为集群还不够大,还没有遇到做注册中心的机器一半以上都挂掉的情况,

Feign的底层原理

底层基于动态代理发送http请求,然后通过负载均衡算法调到对应的服务机器上

Eureka的底层原理

服务注册与发现

客户端启动时会将自己的IP端口服务名称等信息注册到服务端

心跳与故障检查

客户端默认30s向服务端发送一次心跳,也被称为续约,默认情况下服务器在90s内没有收到心跳,就会把该服务移除

自我保护机制

如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制 ,1.不会在移除因为长时间没有收到心跳二应该过期的服务,2.仍然能够接受新服务的注册请求3.当网络稳定时,新的注册信息会被同步到其他节点中

如果Eureka挂掉 各个服务之间还可以正常调用嘛

可以.前提是微服务的地址没变.当一个微服务第一次通过eureka获得另一个微服务的地址,后面知道地址后直接去调用,无需经过注册中心

mybaits 为什么通过mapper接口可以连接到数据库

mybatis是通过sqlSession这个入口才能访问数据库;首先通过动态代理在内存中生成mapper接口的实现类,实现类中的方法要做三个翻译,第一将请求转发给sqlSession中指定的方法,第二拼接sql语句的唯一标识(nameSpace + id),第三传递查询参数;通过这三步翻译,基于sqlSession完成对数据库的访问

为什么feign可以调用其他对象

feign通过动态代理生成实现类

Redis的集群模式有哪些?他们之间有什么区别?

主从模式

能够实现数据的备份,分担主服务器的压力,提高了并发量,读写分离

主从模式刚刚链接时候进行全量同步,全量同步结束后,在进行增量同步

缺点:主服务器宕机时,如果没有进行数据同步,就会造成数据的丢失,还需要去手动切换主服务器,费时费力,还可能会造成一段时间不能使用的情况, 存储的数据受到某台机器的内存容量,所以不可能⽀持特⼤数据量

哨兵模式

哨兵模式是一种特殊的模式,哨兵是一个独立的进程,可以独立运行,主从模式有的它都有,通过心跳检查主服务器宕机时,通过算法自动选举出新的主服务器

缺点: 网络情况不好选举需要花费时间,可能导致服务不可用.不能很好的解决redis容量上限问题

集群模式

从redis3.0之后开始使用,将多个主从模式的结构组合起来,使多个节点同时执行写的操作,每个节点都能保存数据,提高性能

synchronized锁升级,是无法降级的在jdk1.6版本后新增锁升级机制,来平衡性能和数据安全性

1.无锁

当一个对象被创建时,还没有线程进入,就是无锁状态

2.偏向锁

把当前的锁偏向于某个线程,通过CAS机制修改某个锁的一个标记,适合在同一个线程多次在申请同一个锁

3.轻量级锁

轻量级锁并没有把线程阻塞挂起,而是让线程空循环等待,串行执行 ,只适合于线程交替执行

多线程进来竞争锁,会转化为重量级锁

4.重量级锁

也被称为互斥锁.除了拥有锁的线程以外其他线程都阻塞,防止CPU空转

互斥锁

互斥锁就是说,当 key 失效的时候,让一个线程读取数据并构建到缓存中,其他线程就先等待,直到缓存构建完后重新读取缓存即可。

mybaits 为什么通过mapper接口可以连接到数据库

mybatis是通过sqlSession这个入口才能访问数据库;首先通过动态代理在内存中生成mapper接口的实现类,实现类中的方法要做三个翻译,第一将请求转发给sqlSession中指定的方法,第二拼接sql语句的唯一标识(nameSpace + id),第三传递查询参数;通过这三步翻译,基于sqlSession完成对数据库的访问

MyBatis 的接口mapper可以重载么

不可以,mybatis查找mapper内的方法是靠方法名,和参数无关。所以,对于mapper接口,Mybatis禁止方法重载

多线程

线程安全

线程安全指的是多个线程访问一个对象时,不考虑运行环境下的调度和交替执行,不需要进行同步,此对象的行为都能获得正确的结果,那么这个对象就是线程安全的

Lock的底层原理

底层是基于AQS实现的,每个都有自己的内部类

  • lock的存储结构:一个int类型(用来存放锁的状态变更) 一个是双向链表(用于存储等待中的线程)
  • lock获取锁的过程:本质上是通过CAS来修改状态,如果获取到锁之后就更改锁的状态,没有获取到锁就放到等待链表中进行等待。
  • lock释放锁的过程:修改状态,调整链表

lock一般使用ReentrantLock类做为锁,配合lock()和unlock()方法。在finally块中写unlock()以防死锁

atomic底层原理

不是传统意义上的锁机制,而是无锁化的CAS机制,通过该机制保证多个线程修改一个数值的安全性,原子性

线程池中最大线程数 核心线程数 如何设置

根据 所需要的cpu密集度 和 IO密集度

查看CPU信息 more /proc/cpuinfo | grep "model name" 出现几行就是几核 CPU密集型:核心线程数 = CPU核数 + 1 IO密集型:核心线程数 = CPU核数 * 2

CPU密集型 可以理解为 就是处理繁杂算法的操作,对硬盘等操作不是很频繁,比如一个算法非常之复杂,可能要处理半天,而最终插入到数据库的时间很快。

IO密集型可以理解为简单的业务逻辑处理,比如计算1+1=2,但是要处理的数据很多,每一条都要去插入数据库,对数据库频繁操作

sychronized和ReentrantLock的区别

sychronized是⼀个关键字,ReentrantLock是⼀个类

1.用法不同

synchronized 可用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用在代码块上

2.获取锁和释放锁方式不同

synchronized 会自动加锁和释放锁 , 而 ReentrantLock 需要手动加锁和释放锁

3.锁类型不同

synchronized 属于非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁

4.底层实现不同

synchronized 是 JVM 层面通过监视器(Monitor)实现的,而 ReentrantLock 是通过 AQS(AbstractQueuedSynchronizer)程序级别的 API 实现

  1. sychronized是⼀个关键字,ReentrantLock是⼀个类
  2. sychronized会⾃动的加锁与释放锁,ReentrantLock需要程序员⼿动加锁与释放锁
  3. sychronized的底层是JVM层⾯的锁,ReentrantLock是API层⾯的锁
  4. sychronized是⾮公平锁,ReentrantLock可以选择公平锁或⾮公平锁
  5. sychronized锁的是对象,锁信息保存在对象头中,ReentrantLock通过代码中int类型的state标识来 标识锁的状态
  6. sychronized底层有⼀个锁升级的过程

乐观锁和悲观锁

悲观锁:悲观锁认为对于同一个数据的并发操作一定是会发生修改的,采取加锁的形式,悲观地认为,不加锁的并发操作一定会出问题。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中Synchronized和ReentrantLock等独占锁就是悲观锁思想实现的

乐观锁:乐观锁正好和悲观锁相反,它获取数据的时候,并不担心数据被修改,每次获取数据的时候也不会加锁,只是在更新数据的时候,通过判断现有的数据是否和原数据一致来判断数据是否被其他线程操作,如果没被其他线程修改则进行数据更新,如果被其他线程修改则不进行数据更新

公平锁和非公平锁

公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁,会让每个线程都去执行.会让线程进入休眠,唤醒,在休眠的状态,执行效率慢

非公平锁:每个线程获取锁的顺序是随机的,不会遵循先来先得的规则,所有线程会竞争获取锁,,synchronized和reentraklock默认(可以手动指定其为公平锁)非公平锁,获取流程:会先通过CAS去获取锁,获取成功就拥有锁,获取失败就进入队列等待,系统默认都使用非公平锁,执行效率高

非公平锁注重的是性能,公平锁注重的是资源的平均分配

Mysql数据库

MySQL主从复制

将数据从主服务器复制到一个或多个从节点,一般采用异步复制的模式

主从复制的作用:实现数据的备份,提高性能,读写分离

主节点执行完客户端提交的任务后会立即提交事务并返回给客户端, 并不关心 log dump 线程是否成功地将将此次事务写进 binglog 并且发送给从库 ,性能好,容易导致主从数据不一致

主从同步的原理

1.master(主)提交完事务后,写入binlog

2.slave连接到master,获取binlog

3.master创建dump(转存)线程,推送binglog到slave(从)

4.slave启动一个IO线程读取同步过来的master的binlog,记录到relay log中继日志中

5.slave再开启一个sql线程读取relay log事件并在slave执行,完成同步

6.slave记录自己的binglog

1.(主库宕机后,数据会丢失)由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了

2.从库的sql线程是单线程,当主线程读写并发量很大时,主从复制会发生延迟

半同步复制

当主库在执行完客户端提交的事务后不是立即提交事务,而是等待 log dump 线程将此次事务同步到binlog 发送给从库,并且至少一个从库成功保存到其relay log中,此时主库的才提交事务并返回客户端

优点:相比于异步模式,半同步方式一定程度上保证了数据同步的可靠性。

缺点:增加了主库响应客户端的延时,延时至少为一个 TCP/IP 的往返时间,即 binglog 发送给从库至收到从库的响应时间2.如果网络异常会卡住主库,直到超时或从库恢复

3、全同步复制

全同步方式,当主库在执行完客户端提交的事务后,必须等待此次的binlog发送给从库,并且所有从库成功地执行完该事务后,主库才能返回客户端。其与半同步复制的区别如下:半同步下,主库等待binlog写入到从库的relay log即可返回,全同步方式下,必须等到从库执行事务成功。

半同步下,至少一个从库响应后主库即可返回客户端,全同步下必须等待所有的从库返回。

优点:对比半同步复制方式,全同步复制方式数据一致性的可靠性进一步提高

缺点:执行事务时,主库需要等待所有的从库执行成功后才能返回,所以会大大提高主库的响应时间。

解决Mysql主从同步失败的问题

  1. 在从数据库中,使用SET全局sql_slave_skip_counter来跳过事件,跳过这一个错误,然后执行从下一个事件组开始
  2. 在从数据库中,重新连上主数据库。这种操作会直接跳过中间的那些同步语句,可能会导致一些数据未同步过去的问题,但这种操作也是最后的绝招。最好就是令从数据库与主数据库的数据结构和数据都一致了之后,再来恢复主从同步的操作

MVCC的原理

类似于乐观锁,在数据库里面包含有事务的标记相当于id,版本号,还有时间戳,在处理过程中会查询最新的

将历史信息在快照内存中,其他事务发生删除修改操作,都是对他不可见的

间隙锁的原理

读取数据的该行与上一行和下一行有一个间隙的锁定,保证在此范围内读取到的数据是一致的,在使用过程中,为了解决幻读的情况,会在后面加上个条件,比如id大于某某,把可能出现出现问题的都去给他上锁,间隙锁

自旋锁

如果持有锁的线程能够在很短的时间内释放锁,等待中的线程就不需要做休眠唤醒,它们只需要通过自旋等一等,一但释放锁就去立即获得锁,减少了资源的消耗,自旋锁是需要消耗cpu的,所以需要设定一个自旋的最大等待时间,如果超过最大等待时间还没有获取到锁,这时会进入阻塞状态

MySql的锁的类型

mysql锁分为共享锁和排他锁,排它锁又细分为表锁和行锁

  1. 共享锁:也就是读锁,⼀个事务给某⾏数据加了读锁,其他事务也可以读,但是不能写
  2. 排它锁:也就是写锁,⼀个事务给某⾏数据加了写锁,其他事务不能读,也不能写

排他锁在数据库操作的时候, 用for update加锁,这个不执行完,其他不能执行,是锁表的

Mysql的三种日志

binlog、redlog和undo log

binlog:是用于记录数据库表结构和表数据变更的二进制日志,(增删改),不记录查的日志, 用任何存储引擎的 mysql 数据库都会记录 binlog 日志

使用场景:主从复制和数据恢复 当需要恢复数据时,可以取出某个时间范围内的binlog进行重放恢复即可

redolog(重做日志):保证持久性 记录数据操作之后的变化,记录的是数据修改之后的值,不管事物是否提交都能记录下来,能够恢复数据

undolog(回滚日志):保证原子性和一致性 记录数据被修改前的样子,防止丢失数据,用于事务失败后的回滚

记录每一条数据变更的逆向过程,添加会去记录删除,修改会记录修改前的值

MVCC版本并发控制

是基于undolog进行实现的,当我们的用户进行一条记录读取的时候,而这条记录又被其他事务占用,那么读取的事务可以通过undolog来获取到原有的数据

什么是死锁?怎么解决?

何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象

JAVA

static关键字

1.static修饰成员方法:一般称作静态方法,只能通过类来访问,没有this,静态方法中不能访问类的非静态成员方法和非静态成员变量,非静态成员方法和非静态成员变量都必须依赖对象来调用。但是,非静态成员方法可以访问静态成员方法和静态成员变量。

2.static修饰成员变量:一般称作静态变量,一般是在类中直接使用,表示该变量在类初次被加载的时候就已经定义好了。

3.static修饰代码块:static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来依次执行每个static块,并且只会执行一次。static块的优点是可以优化程序性能,因为它只在类初次被加载的时候执行一次。

注: static关键字不会改变变量和方法的访问权限。

非静态成员方法中可以通过this访问静态方法成员变量。

java中不允许使用static修饰局部变量

final关键字

修饰的类不能被继承

修饰的方法不可以被重写

修饰的变量不能被改变,如果修饰引用,表示引用不可变,指向引用的内容可变

修饰的常量,会在编译阶段存入常量池中

修饰的方法,JVM会尝试将其内联,以提高运行效率

说说LIST,SET,MAP三者的区别

List(排序) 存储的是不唯一有序的对象

Set(注重独一无二) 不允许重复的集合

Map 存放的是键值对 key和value

Java死锁如何避免

造成死锁的原因

  1. ⼀个资源每次只能被⼀个线程使⽤
  2. ⼀个线程在阻塞等待某个资源时,不释放已占有资源
  3. ⼀个线程已经获得的资源,在未使⽤完之前,不能被强⾏剥夺
  4. 若⼲线程形成头尾相接的循环等待资源关系

如何避免

  1. 要注意加锁顺序,保证每个线程按同样的顺序进⾏加锁
  2. 要注意加锁时限,可以针对所设置⼀个超时时间
  3. 要注意死锁检查,这是⼀种预防机制,确保在第⼀时间发现死锁并进⾏解决

数据结构

数组:查询效率快,增加删除速度慢.需要换key所以慢

链表: 增加删除效率快,查找效率慢 只需要改变前后标识

hash表: 无法排序,无法做区间查询,容易产生hash冲突,适用于等值查询

二叉树: 每个节点最多有两个子节点,小于放左边,大于放右边,极端情况下会形成单向链表,查询效率低

AVL(平衡二叉树):每个节点最多有两个子节点,小于放左边,大于放右边,通过旋转保持二叉树平衡

红黑树:根节点默认黑色,通过旋转保证树的平衡,是一个非严格的平衡二叉树,如果节点是红色的,它的子节点必须是黑色的

B树(B-树)最多只有三层,一个节点可以存入多个数据块,高度变小,查询速度提升

B+树:在B数的基础上做优化,最下面的为叶子节点,其他的为非叶子节点

⼆叉搜索树和平衡⼆叉树有什么关系?

平衡二叉树是二叉搜索树的升级,大的放右边,小的放左边. 平衡⼆叉搜索树是规定 节点左右两边 的⼦树⾼度差的绝对值不能超过1

强平衡⼆叉树和弱平衡⼆叉树有什么区别

强平衡⼆叉树AVL树,弱平衡⼆叉树就是我们说的红⿊树。

  1. AVL树⽐红⿊树对于平衡的程度更加严格,在相同节点的情况下,AVL树的⾼度低于红⿊树
  2. 红⿊树中增加了⼀个节点颜⾊的概念
  3. AVL树的旋转操作⽐红⿊树的旋转操作更耗时

为什么索引底层实现选择B+树

1.新增了叶子节点,叶子节点存的是类似链表有序的数据,所以可以排序或区间查询

2.利用了磁盘的预读能力,按照kb去读取,在读取数据时,会把该数据的兄弟节点也读取到,减少了IO的次数,需要读其他数据不需要在查找了,直接在缓存中查找出来,非叶子节点的数据都是按页存储,默认为16KB

3.在B树上改造的,树的高度受到限制,提升查询效率,性能提升

Hash索引和B+索引

hash索引 等值查询时使用,不适合范围查询,数据量多的情况会出现hash冲突

Mysql存储引擎

innodb和myisam

1.Innodb聚集索引(数据文件和索引文件放在一起的),Myisam非聚集索引(数据文件和索引文件分开的)

2.Innodb支持事务,Myisam不支持事务的

3.Innodb是行级锁(必须使用到索引列才能去使用否则是表锁),Myisam表级锁

4.Innodb具有自动崩溃恢复功能,Myisam崩溃后无法安全恢复

一级索引和二级索引

走主键的索引为一级索引,非主键索引为二级索引(先通过普通索引找到主键,再通过主键回表找到数据)

如果进行回表操作一级索引快

如果没有回表操作,不确定谁快

如果查询的列只包含索引列,不需要回表操作,叫索引覆盖

explain索引的级别

type字段查询指标从快到慢,保证查询至少达到range级别,最好能达到ref,

All 全盘扫描,性能最差, index和全盘扫描差不多,避免了排序

range 有限制的索引扫描 ref 速度较快

查看表中所有索引

  1. show index from table;
  2. show keys from table;

mysql里的%和_的区别

%是匹配后面所有的字段 _是匹配单个字段

mysql建索引的情况

哪些情况需要建索引

1.主键需要创建唯一索引 2.频繁需要作为查询条件的字段 3.外键索引 4.需要排序的字段5.统计分组字段

哪些情况不需要创建索引

1.频繁更新的字段不适合 2.where条件里用不到的字段 3.重复率较大的字段

索引下推

简称ICP,在mysql5.6之后,能够减少回表的查询次数,提高查询效率

就是将服务层server负责处理的东西交给存储引擎去做处理

组合查询时,select * from t_user where name like '张%' and age=10;满足最左匹配原则,5.6之前只查询了前面的条件,server层再对查询的条件做一次筛选

5.6之后,会把所有的条件在存储引擎中查找出来,减少了回表的次数

注意:引用了子查询的条件不能下推;

引用了存储函数的条件不能下推,因为存储引擎无法调用存储函数

消息队列

MQ的刷盘机制

RocketMQ的消息会在消息的发送者发送到队列之后存储到本地磁盘上

mq的工作模式

简单模式(队列模式)(点对点发送):一个生产者对应一个消费者 ,

工作模式(广播模式):一个生产者对应多个消费者,一次只能被一个消费者拿到消息,消费者不会拿到重复的消息

消息订阅模式(经常使用):多了个交换机,生产者发送给交换机,所有消费者都能收到消息

路由模式:和订阅模式相同,传递过程中加了key,key匹配上才能拿到消息

Redis

redis为什么这么快,用了NIO多路复用

1:完全基于内存操作

2:C语言实现,优化过的数据结构,基于几种基础的数据类型,redis做了大量的优化,性能极高

3:使用单线程,无上下文的切换文本

4:基于非阻塞NIO的多路复用机制

redis是单线程,所有的操作都是按照线程顺序执行,容易造成阻塞,io多路复用就是为了解决这一问题

I/O 多路复用其实是在单个线程中通过记录跟踪每一个sock(I/O流) 的状态来管理多个I/O流 , 可以同时监察多个流的 I/O 事件的能力 , 在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量

redis内存容量增加后,会带来什么问题

  1. 内存快照RDB生成和恢复的效率低
  2. 主从节点全量同步时间增长,缓冲区溢出

如何保证缓存和数据库的一致性

  1. 缓存只读,缓存只提供数据的读取
  2. 当数据库的数据修改时,实时同步到数据库中(先更新数据库,再更新缓存)

redis是单线程吗?为什么redis这么快

redis是单线程,但是在6.0之后出现了多线程,redis单线程是因为他可以避免上下文的切换,效率高,redis是基于内存的,它的瓶颈并不是CPU而是内存和网络,如果有网络的延迟的话,会读取不到数据,所以说采用多线程的形式,用来避免这种情况的发生

Redis 的持久化?

为什么需要持久化:redis是基于内存的,如果晕倒了进程退出,服务器宕机等意外情况.没有持久化机制,无法对redis中数据进行恢复.

RDB:把当前数据生成快照保存在硬盘上

AOF:记录每次对数据的操作到硬盘上

redis持久化方式有RDB和AOF ,RDB是方式是每过几秒保存的是redis数据的快照,但是可能会丢数据,AOF 保存的是所有在redis执行的命令,它会追加到一个文件里面,丢数据可能性小,但会导致文件很大,假如redis宕机了,恢复的时候会很慢,我们一般使用RDB,因为我们对redis的定位就是缓存服务器,很重要的数据我们不会存redis,比如与钱有关系。

redis有哪几种数据类型

string: 可以⽤来做最简单的数据缓存,可以缓存某个简单的字符串,也可以缓存某个json格式的字符 串,Redis分布式锁的实现就利⽤了这种数据结构,还包括可以实现计数器、Session共享、分布式ID

hash: 可以⽤来存储⼀些key-value对,更适合⽤来存储对象

list: Redis的列表通过命令的组合,既可以当做栈,也可以当做队列来使⽤

set: 和列表类似,也可以存储多个元素,但是不能重复,集合可以进⾏交集、并集、差集操作,从⽽ 可以实现类似,我和某⼈共同关注的⼈、朋友圈点赞等功能

zset : 集合是有序的,有序集合可以设置顺序,可以⽤来实现排⾏榜功能

redis中的keys和scan对比

keys和scan都是用来返回key的

kyes返回所有符合条件的key,比较精准,并且不会重复,数据量比较大的时候时间是比较久的,可能会影响其他线程

scan 是利用游标, 迭代每次返回一部分 key, 并不是全部,速度快

总结:数据量小时使用KEYS,反之使用SCAN

Redis淘汰策略

随机淘汰一个key

淘汰一个不常用的key

设置过期时间随机淘汰一个key

设置过期时间淘汰一个不常用的key

设置过期时间淘汰ttl最小的key

淘汰只能读不能写的key

Redis过期策略

1.定时删除:创建定时器,到时间立即执行删除操作

2.惰性删除:每次获取键时检查是否过期,过期就删除

3.定期删除:隔一段时间就检查一次

雪崩 穿透 击穿

缓存击穿是指:一条热点数据key在查询的时候突然过期了,那么就所有的请求都打在数据库上

解决方法: 让这个key永不过期

加锁,只让一个人去访问数据库并且将访问的数据存入缓存中,供其他的请求来访问,

缓存雪崩是指:由于缓存中的数据一下子全部都在同一时间过期了,所以发送过来的全部请求都去请求数据库,导致数据库难以承受而宕机

\1. 可以保证 redis高可用,建集群

\2. 设置不同的过期时间,防止全部在同一时间过期

缓存穿透是指:缓存和数据库中都没有数据,如果有人恶意访问的会先去缓存查询,此时缓存中无数据,后在去数据库中去查询,数据库中也没有,这个时候就会导致数据库宕机

\1. 可以在缓存中设置一个null值,让恶意的请求不会直接击垮数据库,每次访问的时候都去访问此缓存

\2. 可以设计一个过滤器,常用的就是布隆过滤器(可以缓解,为什么是缓解,因为使用过滤器还会造成误判的情况)

布隆过滤器 是一个叫布隆的人提出的,他是很长的二进制向量,既然是2进制的那么除了0就是1

优点:由于存放的是不完整数据,所以内存占用少,添加和查询的时候比较快

缺点:随着数据的不断增加,肯定会有不同的数据通过固定的算法算出来的结果都是一样的,所以会出现误判情况,并且是没有办法删除的,因为一个下标上放置的不仅仅是一个数据,它只能去判断这个数据是否一定不存在,不能判断数据是否一定存在。(就是通过下标来判断的)

它只能去判断这个数据是否一定不存在,不能判断数据是否一定存在

reids脑裂问题

由于网络波动导致主服务器没有被哨兵检测到,生成新的主服务器,出现了两个主服务器,会导致数据的丢失,造成数据的不同步.

解决方法在配置中设置

min-slaves-to-write(最小从服务器数)

min-slaves-max-lag(从连接的最大延迟时间)

redis热key

在Redis中,我们把访问频率高的Key,称为热Key

产生原因:用户消费的数据远大于生产的数据,如商品秒杀、热点新闻、热点评论等读多写少的场景

可能会造成的影响:缓存击穿

如何解决热Key问题

1、Redis集群扩容:增加分片副本,分摊客户端发过来的读请求;

2、使用二级缓存,即JVM本地缓存,减少Redis的读请求。

JVM

jvm分区

栈内存是线程私有的,堆内存是线程公有的

Heap (堆区):主要存储new出来的对象实例,Java堆中细分为:新生代和老年代,一个新生代分为1个Eden区和2个Survivor区,说明:绝大部分对象在Eden区生成,当Eden区装填满的时候,会触发Young Garbage Collection,即YGC。垃圾回收的时候,在Eden区实现清除策略,没有被引用的对象则直接回收。依然存活的对象会被移送到Survivor区。Survivor区分为so和s1两块内存空间。每次YGC的时候,它们将存活的对象复制到未使用的那块空间,然后将当前正在使用的空间完全清除,交换两块空间的使用状态。如果YGC要移送的对象大于Survivor区容量的上限,则直接移交给老年代

元空间区:jdk1.7的方法区移到了元空间,比如类元信息、字段、静态属性、方法、常量等都移动到元空间区,元空间并不在虚拟机中,而是使用本地内存

栈区:栈里面存的都是一些局部变量,比如8大基本数量类型,还有线程运行,方法运行都在栈里面,另外创建对象的时候的引用也是存在栈里面的

本地方法栈和虚拟机栈:存的是一些native方法

程序计数器:是一块较小的内存空间。是线程私有的。它可以看作是当前线程所执行的字节码的行号指示器

方法区:jdk1.7之前为永久代1.8为元空间

Jdk1.7到Jdk1.8 java虚拟机发⽣了什么变化?

JDK8之前将堆空间分为 新生代+老年代+永久代

JDK8之后将堆空间分为 新生代+老年代+元空间

元空间的好处

区别:1.元空间不在虚拟机中,而是使用本地内存

2.默认情况下,元空间大小仅受本地内存限制

方法区所存储的信息都是比较难确定的,对于方法区的大小是比较难指定的,太小了容易造成方法区溢出,太大了又会占用太多虚拟机的内存空间, ⽽转移到本地内存后则不会影响虚拟机所占⽤的内存

类加载

类加载器有这几个:

启动类加载器:jvm启动的时候,会优先加载<JAVA_HOME>\lib这个目录的核心类库。

扩展类加载器:负责加载<JAVA_HOME>\lib\ext这个目录的类。

应用程序类加载器:负责加载我们写的代码。

自定义类加载器:根据我们的需要,加载特定的类。

垃圾回收算法?

1.标记清除2.复制算法3.标记管理4.分代收集算法

JVM调优的几种场景

CPU占用过高的原因:死循环,递归,计算量大,线程数过多

用top命令查看cpu占用情况

用top -H-p命令查看线程的情况,把线程号转换为16进制,用jstack工具查看线程栈情况

用jstat分析gc活动情况

如何排查JVM问题

  1. 可以使⽤jmap来查看JVM中各个区域的使⽤情况
  2. 可以通过jstack来查看线程的运⾏情况,⽐如哪些线程阻塞、是否出现了死锁
  3. 可以通过jstat命令来查看垃圾回收的情况,特别是fullgc,如果发现fullgc⽐较频繁,那么就得进⾏调 优了
  4. 通过各个命令的结果,或者jvisualvm等⼯具来进⾏分析

双亲委派

类加载器进行类加载时,会先将请求委托给自己的父类加载器执行,直到顶层的启动类加载器,父类加载器能够完成加载则成功返回,不能则子类加载器才自己尝试加载,能够保证系统的安全

优点 避免类的重复加载,提高系统的安全性,避免类库被篡改

从当前类加载器逐层向上寻找,如果找到就使用上级,否则就使用用户使用的类

线程之间如何进⾏通讯的

线程之间可以通过共享内存或基于⽹络来进⾏通信 , 如果是通过共享内存来进⾏通信,则需要考虑并发问题,什么时候阻塞,什么时候唤醒 , 像Java中的wait()、notify()就是阻塞和唤醒

通过⽹络就⽐较简单了,通过⽹络连接将通信数据发送给对⽅,当然也要考虑到并发问题,处理⽅式就 是加锁等⽅式

JAVA的四种引用数据类型

1.强引用: 把一个对象赋给一个引用变量,这个引用变量就是一个强引 用 ,不会被垃圾回收机制回收,索引会造成内存泄漏

2.软引用: 当系统内存足够时,不会被回收

3.弱引用: 只要垃圾回收机制一运行,不管内存够不够都会被回收

4.虚引用: 它不能单独使用,必须和引用队列联合使用。虚 引用的主要作用是跟踪对象被垃圾回收的状态

雪花算法

使用64位long类型的数字作为全局唯一id,并且id有时间戳的引入,保持了自增

第一个部分,是 1 个 bit:0,这个是无意义的。

第二个部分是 41 个 bit:表示的是时间戳。

第三个部分是 5 个 bit:表示的是机房 id,10001。

第四个部分是 5 个 bit:表示的是机器 id,1 1001。

第五个部分是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 id 的序号,0000 00000000

雪花算法的优点

id自增:索引效率高

高性能 高可用:在内存中生成,不依赖于数据库

容量大:可以在每秒中生成数百万个自增的id

雪花算法缺点

它在生成的时候引入了时间戳,如果系统时间被回调,或者更改,会导致id重复;而解决这个问题的方法是可以进行算法的更改,抽出将10bit的机器去id优化,改成与业务表相关的

使用自增id的缺点

  • 不具有连续性,如果说中途删除了最大值,那么下一个生成的时候就会出现跳号(1,2,3最大是3 那么吧3删除了,下一次就会出现4,那么此时数据库中就有1,2,4)
  • 如果在进行合表的情况下,历史表的主键id会和现在的数据表有一个id的重复

Mysql里锁的类型有哪些呢

  1. 分为共享锁和排他锁
  2. 读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写
  3. 写锁是排他锁,它会阻塞其他的写锁和读锁。从颗粒度来区分,可以分为表锁和行锁两种
  4. 表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表
  5. 行锁又可以分为乐观锁和悲观锁,悲观锁可以通过for update实现,乐观锁则通过版本号实现。

6.区间锁:锁的是一个区间

数据库优化

优化方式:建索引,读写分离,分区表,分库分表

为什么要分库分表

单个数据库数据大会出现性能问题

分库分表

1.只分库不分表:读或者写的qps过高,数据库的连接不够用,通过增加数据库实例的方式,增加数据库的连接,提高并发度

2.只分表不分库:alibaba规范手册单个表数据达到2千万或者表容量达到2G时,要进行分表

3.既分库又分表:数据库的连接不够,单表的数据量大

分库分表有什么弊端

  • 表关联,left join存在问题
  • 事务问题
  • 取件查询性能比较低

分库分表的框架

mycat(免费的,公司不用,需要专门的人去维护)

TDDL(阿里巴巴,收费高)

shareding-jdbc(免费的,需要jar,自己设定规则,有一个虚拟的业务逻辑表)

分表方式

  • 垂直划分(数据库表字段多,拆分成多张表,将一张表中的字段拆成多张)
  • 水平拆分(表结构一样,表里面的数据不一样,将数据进行拆分)

分表规则

  • id取模方式,id通过hash算法去模运算(比如要分成两个表,总共有3条数据,1%2=1,那么编号为1的就放在第2个表中)
  • 根据季度和月去划分

进程和线程

1..进程是程序中资源分配的最小单位,线程是程序最小执行单位

2.一个进程可以包含多个线程

3.不同进程间数据很难共享

4.同一进程下不同线程间数据很易共享

5.进程要比线程消耗更多的计算机资源

6.进程间不会相互影响,一个线程挂掉将导致整个进程挂掉

7.进程可以拓展到多机,进程最多适合多核

8.进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存

9.进程使用的内存地址可以限定使用量

并发编程三要素?

  1. 原⼦性:不可分割的操作,多个步骤要保证同时成功或同时失败
  2. 有序性:程序执⾏的顺序和代码的顺序保持⼀致
  3. 可⽤性:⼀个线程对共享变量的修改,另⼀个线程能⽴⻢看到

mysql 为什么使用innodb做批量添加:

当执行批量插入,按主键顺序插入行更快,InnoDB 表使用聚簇索引,这种索引使按主键顺序使用数据相对快速,按照主键顺序执行批量插入,对于不完全适合于缓冲池的表,是特别重要的。

当mysql在做添加的时候,他需要去添加一级缓存中的数据以及二级缓存中的数据,一级索引中是根据主键排序的,直接加在后面就可以了,但是次级索引的顺序不确定,所有每次添加的时候不知道将数据添加到哪里,都要先去查找到对应的位置,这样就会造成很大的资源消耗,所以innodb加入了缓冲池

关于两个服务之间redis取不到值时:

1:key是否一致 2:是否连着同一个redis 3:查看两个服务的redis编码格式是否一致 4:是否过期

如何防止重复下单

用户每次从页面上点击下单按钮,发送请求到后台的时候,带上一个唯一的订单号,在数据库层面将这个订单号设置为唯一的,这个时候在添加的时候就不会出现重复订单的问题; 在缓存的层面上存储一个token,用户发送请求的时候带上这个token,在添加订单的时候进行一次校验,使用过一次就会销毁掉,保证每个token只能使用一次,防止重复下单; 1.6:延迟队列消息时间结束时,用户支付失败,但库存预减了--消息过期没有支付,先去第三方查询支付结果 先去第三方查询支付结果,然后根据查询到的结果进行一个操作,如果第三方确定没有支付,那就取消订单,如果第三方支付了,但是因为某些问题,在回调的时候没有修改订单状态,那就对订单状态进行一个修改;

空间局部性原理 磁盘的预读能力, 优化SQL 场景举例:报表统计。需要统计前一天注册的用户数量,每天凌晨跑定时任务 用date format。当时不走索引,优化后使用between...and 走索引了

对账防止被篡改,用户支付成功后,本应用户、支付宝、银行三方显示交易状态为成功。但可能因为出错,银行单方面通知支付宝交易失败,不会修改用户的订单状态,导致出现问题。会在每天的凌晨对前一天的账目,防止出错

没有使用XXL-JOB,定时任务去加redis锁 生产上接口比较慢,怎么排查?首先判断偶尔慢还是经常慢。偶尔慢,可能涉及到网络问题或者服务器问题。试一下本地上快不快,建索引来解决。异步编排。生产慢的话,用阿尔萨斯中的try命令可以看到类或方法的调用时长。

吃内存比较厉害,但是还没有内存溢出,怎么解决? jstat看GC的回收频率,查看新老生代的回收频率。jump文件

深拷贝和浅拷贝都是对象的拷贝

深拷贝:既会拷贝基本数据类型的值,也会复制一份对象的引用地址所指向的对象,内部的属性指向的不是同一个对象

浅拷贝:只会拷贝基本数据类型的值

@Component和@Bean有什么区别

  1. @Component 只能注解在类(ElementType.Type)中 @Bean 只能注解方法(ElementType.Method)中

  2. @Component (也包括@Controller、@Service、@Repository、@Configuration等)是通过类路径扫描来自动侦测并放入Spring容器中

  3. @Component 一般用于无状态的一些Bean中(没有类变量),例如功能、服务性的Service、Controler等

    @Bean 一般用于需要带有状态的Bean中(有类变量),例如容器类的Bean、配置类的Bean等

静态代理和动态代理

静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。静态代理运行前就知道自己代理的对象 动态:在程序运行时运用反射机制动态创建而成。运行后才知道要代理的对象

删除表数据后表的大小却没有变动,这是为什么

在使用 delete 删除数据时,其实对应的数据行并不是真正的删除,是**「逻辑删除」,InnoDB 仅仅是将其「标记成可复用的状态」**,所以表空间不会变小

为什么 VarChar 建议不要超过255?

当定义varchar长度小于等于255时,长度标识位需要一个字节(utf-8编码)

当大于255时,长度标识位需要两个字节,并且建立的索引也会失效

Mysql 中有哪些锁

  • 基于锁的属性分类:共享锁、排他锁
  • 基于锁的粒度分类:表锁、行锁、记录锁、间隙锁、临键锁
  • 基于锁的状态分类:意向共享锁、意向排它锁、死锁