精心整理的java八股,三万字!!!

请你说说线程和协程的区别

1.调度方式:线程是由操作系统进行调度的,它们的切换需要操作系统的介入,而协程则是由程序本身进行调度的,切换时不需要操作系统介入,切换速度更快。

2.资源占用:线程是操作系统管理的资源,创建和销毁的开销比较大,而协程则是程序内部的协作机制,创建和销毁的开销比较小,不需要操作系统管理。

3.并发性能:由于线程需要操作系统管理和调度,所以线程的并发性能相对较差,而协程由程序自身控制,能够实现更高的并发性能。

4.状态保存:线程切换时需要保存和恢复线程的状态,包括程序计数器、寄存器等,而协程切换时只需要保存和恢复自己的状态,不需要保存整个进程的状态。

5.通信方式:线程之间通信的方式比较复杂,如使用锁、信号量、管道等,而协程之间可以使用更简单的方式进行通信,如共享变量、管道等。

请你说说线程和进程的区别

1.资源使用:进程拥有独立的内存空间,因此不同的进程之间互相独立,它们可以拥有自己的资源,如打开的文件、网络连接、环境变量等。而线程则共享进程的资源,如内存、文件句柄等。

2.调度:进程拥有自己的调度器,它们的切换需要操作系统的介入,而线程则由操作系统的线程调度器进行调度,线程的切换相对于进程切换来说更加轻量级。

3.执行速度:由于线程共享进程的资源,线程之间的切换速度相对较快,因此线程的执行速度一般要比进程快。

4.创建和销毁:创建和销毁一个进程的开销通常比创建和销毁一个线程的开销要大。

5.通信方式:不同进程之间通信的方式比较复杂,如使用共享内存、管道等,而线程之间可以使用全局变量等简单的方式进行通信。

MySQL索引,以及它们的好处和坏处

在大数据量的查询中,合理使用索引的优点非常明显,不仅能大幅提高匹配where条件的检索效率,还能用于排序和分组操作的加速。

当时索引如果使用不当也有比较大的坏处:比如索引必定会增加存储资源的消耗;同时也增大了插入、更新和删除操作的维护成本,因为每个增删改操作后相应列的索引都必须被更新。

MySQL索引是一种帮助快速查找数据的数据结构,可以把它理解为书的目录,通过索引能够快速找到数据所在位置。场景的索引数据结构有:Hash表(通过hash算法快速定位数据,但不适合范围查询,因为需要每个key都进行一次hash)、二叉树(查找和修改效率都比较高),但是在InnoDB引擎中使用的索引是B+Tree,相较于二叉树,B+Tree这种多叉树,更加矮宽,更适合存储在磁盘中。使用索引增加了数据查找的效率,但是相对的由于索引也需要存储到磁盘,所以增加了存储的压力,并且新增数据时需要同步维护索引。但是合理的使用索引能够极大提高我们的效率!

加分回答 只要创建了索引,就一定会走索引吗? 不一定。 比如,在使用组合索引的时候,如果没有遵从“最左前缀”的原则进行搜索,则索引是不起作用的。 举例,假设在id、name、age字段上已经成功建立了一个名为MultiIdx的组合索引。索引行中按id、name、age的顺序存放,索引可以搜索id、(id,name)、(id, name, age)字段组合。如果列不构成索引最左面的前缀,那么MySQL不能使用局部索引,如(age)或者(name,age)组合则不能使用该索引查询。

请你说说多线程

1.线程是程序执行的最小单元,一个进程可以拥有多个线程

2.各个线程之间共享程序的内存空间(代码段、数据段和堆空间)和系统分配的资源(CPU,I/O,打开的文件),但是各个线程拥有自己的栈空间

3.多线程优点:减少程序响应时间;提高CPU利用率;创建和切换开销小;数据共享效率高;简化程序结构

说说怎么保证线程安全

线程安全问题是指在多线程背景下,线程没有按照我们的预期执行,导致操作共享变量出现异常。

在Java中有许多同步方案提供给我们使用,从轻到重有三种方式:原子类、volatile关键字、锁。

原子类是juc atomic包下的一系列类,通过CAS比较与交换的机制实现线程安全的更新共享变量。通过预期值与内存值的比较来判断是否修改。volatile关键字是轻量级的同步机制,他实现了变量的可见性、防止指令重排序。保证了【单个变量】读写的线程安全。

可见性问题是JMM内存模型中定义每个核心存在一个内存副本导致的,核心只操作他们的内存副本,volatile保证了一旦修改变量则立即刷新到共享内存中,且其他核心的内存副本失效,需要重新读取。 原子类和volatile只能保证单个共享变量的线程安全,锁则可以保证临界区内的多个共享变量线程安全。

java中常用的锁有两种:synchronized+juc包下的lock锁。synchronized锁是互斥锁,可以作用于实例方法、静态方法、代码块,基于对象头和Monitor对象,在1.6之后引入轻量级锁、偏向锁等优化。lock锁接口可以通过lock、unlock方法锁住一段代码,基于AQS实现,其加锁解锁就是操作AQS的state变量,并且将阻塞队列存在AQS的双向队列中。除了锁以外,juc包下还提供了一些线程同步工具类,如CountDownLatch、Semaphore等等,我们还可以使用ThreadLocal定义线程局部变量!

请你说说死锁定义及发生的条件

死锁 两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。这些永远在互相等待的进程称为死锁进程。

产生死锁的必要条件 虽然进程在运行过程中,可能发生死锁,但死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用

2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。

3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。

4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。

请你说说进程间的通信方式

进程间通信主要包括:管道、命名管道、信号、消息队列、共享内存、内存映射、信号量、Socket:

管道 管道也叫无名(匿名)管道,它是是 UNIX 系统 IPC(进程间通信)的最古老形式,所有的 UNIX 系统都支持这种通信机制。管道本质其实是内核中维护的一块内存缓冲区,Linux 系统中通过 pipe() 函数创建管道,会生成两个文件描述符,分别对应管道的读端和写端。无名管道只能用于具有亲缘关系的进程间的通信。

命名管道 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO文件。有名管道(FIFO)不同于匿名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,并且其打开方式与打开一个普通文件是一样的,这样即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。

信号 信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

消息队列 消息队列就是一个消息的链表,可以把消息看作一个记录,具有特定的格式以及特定的优先级,对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程则可以从消息队列中读走消息,消息队列是随内核持续的。

共享内存 共享内存允许两个或者多个进程共享物理内存的同一块区域(通常被称为段)。由于一个共享内存段会称为一个进程用户空间的一部分,因此这种 IPC 机制无需内核介入。所有需要做的就是让一个进程将数据复制进共享内存中,并且这部分数据会对其他所有共享同一个段的进程可用。与管道等要求发送进程将数据从用户空间的缓冲区复制进内核内存和接收进程将数据从内核内存复制进用户空间的缓冲区的做法相比,这种 IPC 技术的速度更快。

内存映射 内存映射(Memory-mapped I/O)是将磁盘文件的数据映射到内存,用户通过修改内存就能修改磁盘文件。

信号量 信号量主要用来解决进程和线程间并发执行时的同步问题,进程同步是并发进程为了完成共同任务采用某个条件来协调它们的活动。对信号量的操作分为 P 操作和 V 操作,P 操作是将信号量的值减 1,V 操作是将信号量的值加 1。当信号量的值小于等于 0 之后,再进行 P 操作时,当前进程或线程会被阻塞,直到另一个进程或线程执行了 V 操作将信号量的值增加到大于 0 之时。

Socket 套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。Socket 一般用于网络中不同主机上的进程之间的通信。

说说你对MVC的理解

Spring mvc 是一个基于java的实现了mvc设计模式的轻量级web框架,在这种模式下软件被分为三层,即model、view、Controller。将软件分层的好处是 可 以将对象之间的耦合度降低,便于代码的维护。

model封装了数据和对数据的操作,是实际进行数据处理的地方,view负责进行模型的展示,一般就是我们见 到的用户界面 Controller控制器负责视图和模型之间的交互主要负责两方面的动作,一是把用户的请求分发到相应的模型,二是把模型的改变及时地响应到 视图上。Spring mvc框架已经成为了mvc模式地最主流实现,前端控制器是DispatcherServlet接口实现类,映射处理器是HandlerMapping接口实现类,视图 解析器是ViewResolver接口实现类,页面控制器是Controller接口实现类

详细的说说Redis的数据类型

redis中常用的五种数据结构:string、list、set、zset、hash。String结构底层是一个简单动态字符串,支持扩容,存储字符串。list存储线性有序且可重复的元素,底层数据结构可以是双向链表/压缩列表。set存储不可重复的元素,一般用于求交集、差集等,底层数据结构可以是hash和整数数组,zset存储的是有序不可重复的元素,zset为每个元素添加了一个score属性作为排序依据,底层数据结构可以是ziplist和跳表,hash类型存储的是键值对,底层数据结构是ziplist和hash。redis会在性能以及节省内存间考虑,选择最适合当前状态的底层数据结构实现。

请你说说乐观锁和悲观锁

乐观锁:乐观锁总是假设最好的情况,每次去拿数据的时候默认别人不会修改,所以不会上锁,只有当更新的时候会判断一下在此期间有没有人更新了这个数据。适用于多读,可以使用版本号机制进行控制 悲观锁:悲观锁总是假设最坏的情况,每次去拿数据是都认为别人会修改,所以每次在拿数据时都会上锁,这样别人想拿这个数据时会阻塞直到拿到锁。mysql数据库的共享锁和排他锁都是悲观锁的实现。

设计模式了解么

常用的设计模式有单例模式、工厂模式、代理模式、适配器模式、装饰器模式、模板方法模式等等。

像sping中的定义的bean默认为单例模式,spring中的BeanFactory用来创建对象的实例,他是工厂模式的体现。

AOP面向切面编程时代理模式的体现,它的底层就是基于动态代理实现的。

适配器模式在springMVC中有体现,它的处理器适配器会根据处理器规则适配相应的处理器执行,模板方法模式用来解决代码重复的问题等。

说说你对AOP的理解

AOP面向切面编程。是spring两大核心之一,它是一种编程思想,是对OOP的一种补充。它可以对业务逻辑的各个部分进行隔离,降低耦合,提高代码的可重用性。它的底层是通过动态代理实现的。它的应用场景有事务、日志管理等。

AOP可以有多种实现方式,而Spring AOP支持如下两种实现方式。

JDK动态代理:这是Java提供的动态代理技术,可以在运行时创建接口的代理实例。Spring AOP默认采用这种方式,在接口的代理实例中织入代码。

CGLib动态代理:采用底层的字节码技术,在运行时创建子类代理的实例。当目标对象不存在接口时,Spring AOP就会采用这种方式,在子类实例中织入代码。

说说Redis的持久化策略

1.RDB: redis database 在指定的时间间隔内,将内存中的数据集的快照写入磁盘,文件名dump.rdb 适合大规模的数据恢复,对数据库的完整性和一致性要求不是很高 一定时间间隔备份一次,如果数据库意外down掉,就会失去最后一次快照的所有修改

2.AOF: append only file 以日志的形式记录每个写操作,只允许追加文件,不允许改写文件,redis启动时会读取这个文件,并从头到尾执行一遍,以此来恢复数据,文件名appendonly.aof 在最恶劣的环境下,也丢失不会超过2秒的数据,完整性较高,但是会对磁盘持续的进行IO,代价太大。企业级最少需要5G才能支持 如果.aof文件大小超过原来的一倍,会进行重写压缩,保留最小的指令集合

3.优先级 aof>rdb

请你讲讲单例模式、请你手写一下单例模式

单例模式:一个类对外只提供一个实例

例模式在Java种通常有两种表现形式:

  • 饿汉式:类加载时就进行对象实例化

  • 懒汉式:第一次引用类时才进行对象实例化

  • 饿汉式单例模式: 在类被加载时就会初始化静态变量instance,这时候类的私有构造函数就会被调用,创建唯一的实例。

public class SingleTon{
    private static SingleTon instance= new singleTon();
    private SingleTon(){ } 
    public static SingleTon getInstance(){ 
        return instance; 
    } 
}

请你说说虚拟内存和物理内存的区别

物理内存:计算机中真实拥有的内存。物理内存是有限的,容易产生内存不足问题。

虚拟内存是一种抽象的逻辑概念,拥有连续的内存地址。而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。

说说你对IoC的理解

IoC:控制反转。控制:对象的创建的控制权限;反转:将对象的控制权限交给spring。之前我们创建对象时用new,现在直接从spring容器中取,维护对象之间的依赖关系,降低对象之间的耦合度。 实现方式为DI,依赖注入,有三种注入方式:构造器、setter、接口注入

请你说说内存管理

linux操作系统采用段页式内存管理方式:页式存储管理可以有效的提高内存利用率,段式内存管理能反映程序的逻辑结构并有利于段的共享。将这两种方法结合起来就形成了段页式储存管理方式。段页式储存管理方式就是先建好用户程序分成若干个段,再把每个段分成若干页,并为每个段赋予一个段名。

请你说说IO多路复用(select、poll、epoll)

IO多路复用指的是单个进程或者线程能同时处理多个IO请求,select,epoll,poll是Linux API提供的复用方式。本质上由操作系统内核缓冲IO数据,使得单个进程线程能监视多个文件描述符。select是将装有文件描述符的集合从用户空间拷贝到内核空间,底层是数组,poll和select差距不大,但是底层是链表,这就代表没有上限,而select有数量限制。epoll则是回调的形式,底层是红黑树,避免轮询,时间复杂度从O(n)变为O(1)

请你说说MySQL的事务隔离级别

事务隔离级别是为了解决脏读、不可重复读、幻读

脏读:一个事务读取了另一个事务未提交的数据

不可重复读:事务A两次读取的数据不一致,读第二次之前可能有其他事务修改了这个数据并提交了

幻读:事务A两次读取数据库,两次查询结果的条数不同,称为幻读。行数变了即为幻读,数据变了即为不可重复度

事务隔离级别如下: 读未提交:以上三个问题都解决不了 读已提交:只能解决脏读

可重复读:mysql的默认隔离级别,能解决脏读和不可重复读,包含了间隙锁,可以防止幻读 串行化:都可以解决。(为每个读取操作加一个共享锁)

如何利用Redis实现一个分布式锁?

方案一:SETNX + EXPIRE

方案二:SETNX + value值是(系统时间 + 过期时间)

方案三:使用Lua脚本(包含SETNX + EXPIRE两条指令)

方案四:SET的扩展命令(SET EX PX NX)

方案五:SET EX PX NX + 校验唯一随机值,再释放锁

方案六:开源框架:Redisson

方案七:多机实现的分布式锁Redlock

请说说你对反射的了解

反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它所有的方法和属性,对于任意一个对象,都能知道他的属性和方法。 获取Class对象的三种方式:getClass();xx.class;Class.forName("xxx"); 反射的优缺点: 优点:运行期间能够动态的获取类,提高代码的灵活性。 缺点:性能比直接的Java代码要慢很多。 应用场景:spring的xml配置模式,以及动态代理模式都用到了反射

请你说说ArrayList和LinkedList的区别

ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。

对于随机访问ArrayList要优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问,而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,查找某个元素的时间复杂度是O(N)。

对于插入和删除操作,LinkedList要优于ArrayList,因为当元素被添加到LinkedList任意位置的时候,不需要像ArrayList那样重新计算大小或者是更新索引。

LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。

请你说说聚簇索引和非聚簇索引

两者主要区别是数据和索引是否分离。聚簇索引是将数据与索引存储到一起,找到索引也就找到了数据;而非聚簇索引是将数据和索引存储分离开,索引树的叶子节点存储了数据行的地址。

在InnoDB中,一个表有且仅有一个聚簇索引(因为原始数据只留一份,而数据和聚簇索引在一起),并且该索引是建立在主键上的,即使没有指定主键,也会特殊处理生成一个聚簇索引;其他索引都是辅助索引,使用辅助索引访问索引外的其他字段时都需要进行二次查找。 而在MyISAM中,所有索引都是非聚簇索引,叶子节点存储着数据的地址,对于主键索引和普通索引在存储上没有区别。 加分回答 在InnoDB存储引擎中,可以将B+树索引分为聚簇索引和辅助索引(非聚簇索引)。无论是何种索引,每个页的大小都为16KB,且不能更改。 聚簇索引是根据主键创建的一棵B+树,聚簇索引的叶子节点存放了表中的所有记录。辅助索引是根据索引键创建的一棵B+树,与聚簇索引不同的是,其叶子节点仅存放索引键值,以及该索引键值指向的主键。也就是说,如果通过辅助索引来查找数据,那么当找到辅助索引的叶子节点后,很有可能还需要根据主键值查找聚簇索引来得到数据,这种查找方式又被称为书签查找。因为辅助索引不包含行记录的所有数据,这就意味着每页可以存放更多的键值,因此其高度一般都要小于聚簇索引。

数据库为什么不用红黑树而用B+树?

索引的数据结构会被存储在磁盘中,每次查询都需要到磁盘中访问,对于红黑树,树的高度可能会非常的高,会进行很多次的磁盘IO,效率会非常低,B+树的高度一般为2-4,也就是说在最坏的条件下,也最多进行2到4次磁盘IO,这在实际中性能时非常不错的

请你讲讲工厂模式,手写实现工厂模式

工厂模式:不暴露创建对象的具体逻辑,而是将逻辑封装在一个函数中,那么这个函数就可以被视为一个工厂。;分为简单工厂、工厂方法、抽象工厂模式 简单工厂:提供一个统一的工厂类来创造对象,应用场景:需要创建的对象较少。客户端不关心对象的创建过程 工厂方法:针对不同的对象提供不同的工厂,应用场景: 客户端不需要知道它所创建的对象的类。 客户端可以通过子类来指定创建对应的对象。

手写实现工厂模式可以采用以下步骤:

  1. 创建一个接口,定义创建对象的方法。

public interface ProductFactory {
    Product createProduct();
}
  1. 创建具体的产品类,实现接口中的方法。

public class ProductA implements Product {
    // 具体产品A的实现
}
​
public class ProductB implements Product {
    // 具体产品B的实现
}
  1. 创建工厂类,实现工厂接口,根据不同的参数返回不同的具体产品实例。

public class ConcreteFactory implements ProductFactory {
    @Override
    public Product createProduct(String type) {
        if (type.equals("A")) {
            return new ProductA();
        } else if (type.equals("B")) {
            return new ProductB();
        } else {
            throw new IllegalArgumentException("Invalid product type");
        }
    }
}
  1. 在客户端代码中,通过调用工厂类的方法来创建具体的产品对象。

ProductFactory factory = new ConcreteFactory();
Product productA = factory.createProduct("A");
Product productB = factory.createProduct("B");

在上面的例子中,客户端并不需要知道具体的产品类是什么,只需要通过工厂类来创建对象即可。如果需要更改产品类的实现,只需要修改具体的产品类,而不需要修改客户端的代码。

你知道哪些线程安全的集合?

java.uti包中的集合类大部分都是非线程安全的,例如:ArrayList/LinkedList/HashMap等等,但也有少部分是线程安全的,像是Vector和Hashtable,它们属于很古老的API了,是基于Synchronized实现的,性能很差,在实际的开发中不常用。一般可以使用collections工具类中的syncheronizedXxx()方法将非线程安全的集合包装成线程安全的类。在java5之后可以使用concurrent包提供的大量的支持并发访问的集合类,例如ConcurrentHashMap/CopyOnWriteArrayList等。

请你说说ConcurrentHashMap

一、ConcurrentHashMap的底层数据结构与HashMap一样,也是采用“数组+链表+红黑树

二、采用锁定头节点的方式降低了锁粒度,以较低的性能代价实现了线程安全。

三、实现机制:1. 初始化数组或头节点时,ConcurrentHashMap并没有加锁,而是CAS的方式进行原子替换

  1. 插入数据时会进行加锁处理,但锁定的不是整个数组,而是槽中的头节点。所以,ConcurrentHashMap中锁的粒度是槽,而不是整个数组,并发的性能很好。

  2. 扩容时会进行加锁处理,锁定的仍然是头节点。并且,支持多个线程同时对数组扩容,提高并发能力。 4. 在扩容的过程中,依然可以支持查找操作。

说说缓存穿透、击穿、雪崩的区别

缓存穿透:客户端访问不存在的数据,使得请求直达存储层,导致负载过大,直至宕机。原因可能是业务层误删了缓存和库中的数据,或是有人恶意访问不存在的数据。解决方式:1.存储层未命中后,返回空值存入缓存层,客户端再次访问时,缓存层直接返回空值。2.将数据存入布隆过滤器,访问缓存之前经过滤器拦截,若请求的数据不存在则直接返回空值。

缓存击穿:一份热点数据,它的访问量非常大,在它缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。解决方案:1.永不过期:对热点数据不设置过期时间。2.加互斥锁,当一个线程访问该数据时,另一个线程只能等待,这个线程访问之后,缓存中的数据将被重建,届时其他线程就可以从缓存中取值。

缓存雪崩:大量数据同时过期、或是redis节点故障导致服务不可用,缓存层无法提供服务,所有的请求直达存储层,造成数据库宕机。解决方案:1.避免数据同时过期,设置随机过期时间。2.启用降级和熔断措施。3.设置热点数据永不过期。4.采用redis集群,一个宕机,另外的还能用

Redis如何与数据库保持双写一致性

先更新数据库再删除缓存,若有错误需要重试

共有四种同步策略:1.先更新数据库再更新缓存。缺点:多线程并发下会存在数据库中数据和缓存不一致的的现象。可能出现2.先更新缓存在更新数据库,优点就是每次数据变化都可以及时的更新缓存,但是消耗很大,影响服务器性能。3.先删除缓存在更新数据库。缺点:也会导致缓存和数据库数据不一致。4.先更新数据库再删除缓存。缺点仍然可能存在缓存和数据库中数据不一致的情况,但是,我们可以使用重试机制进行操作。,所以说这是效果最好的解决方案。

说说你了解的线程同步方式

1、Java通过加锁实现线程同步,锁有两类:synchronized和Lock。 2、synchronized加在三个不同的位置,对应三种不同的使用方式,这三种方式的区别是锁对象不同: (1.)加在普通方法上,则锁是当前的实例(this)。 (2.)加在静态方法上,锁是当前类的Class对象。 (3.)加在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。 3、Lock支持的功能包括:支持响应中断、支持超时机制、支持以非阻塞的方式获取锁、支持多个条件变量(等待队列)。

请你说说innodb和myisam的区别?

InnoDB支持事务,默认的锁时行锁。 MyISAM不支持事务,默认的锁时表锁。

读写性能:InnoDB增删改性能更优;MyISAM查询性能更优。

String、StringBuffer、Stringbuilder有什么区别

StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样 String:不可变字符序列,效率低,但是复用率高。

StringBuffer:可变字符序列、效率较高(增删)、线程安全

StringBuilder:可变字符序列、效率最高、线程不安全

请你说说HashMap底层原理

在1.8之前,HashMap的底层是数组加链表,在1.8之后是数组+链表+红黑树; 它的put流程是:基于哈希算法来确定元素位置,当我们向集合存入数据时,他会计算传入的key的哈希值,并利用哈希值取绝对值再根据集合长度取余来确定元素的位置,如果这个位置已经存在其他元素了,就会发生哈希碰撞,则hashmap就会通过链表将这些元素组织起来,如果链表的长度达到8时,就会转化为红黑树,从而提高查询速度。

扩容机制:HashMap中数组的默认初始容量为16,当达到默认负载因子0.75时,会以2的指数倍进行扩容。 Hashmap时非线程安全的,在多线程环境下回产生循环死链,因此在多线程环境下建议使用ConcurrentHashMap。

说说你了解的JVM内存模型

JVM由三部分组成:类加载子系统、执行引擎、运行时数据区

1、类加载子系统:可以根据指定的全限定名来载入类或接口。

2、执行引擎:负责执行那些包含在被载入类的方法中的指令。

3、运行时数据区:分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运算的中间结果等,把这些东西都存储到运行时数据区中,以便于管理。

说说JVM的垃圾回收机制

新生代收集:目标为新生代的垃圾收集。 主要包括serial垃圾收集器,parnew垃圾收集器和parallel scavage垃圾收集器,

老年代收集:目标为老年代的垃圾收集,目前只有CMS收集器会有这种行为。主要包括serialOld垃圾收集器,parallel old垃圾收集器和CMS垃圾收集器,

混合收集:目标为整个新生代及部分老年代的垃圾收集,目前只有G1收集器会有这种行为。 主要包括serialOld垃圾收集器,parallel old垃圾收集器和CMS垃圾收集器,

整堆收集:目标为整个堆和方法区的垃圾收集。serial+serialOld用于客户端的垃圾收集,parnew+CMS用于服务段的垃圾回收,parallel scavage+parallelOld用于后台运算量的系统。

说说类加载机制

加载、验证、准备、解析、初始化

类加载的过程中首先判断这个是否被加载过,如果没有被加载过,那么调用类加载器进行加载,判读这个类是否符合规范,如果不符合就抛出异常,加载成功就会生成class对象。 接下来是链接过程,分为三步:准备,验证,准备,解析。 验证:确保文件符合规范,不会危害虚拟机自身的安全,对文件格式,字节码,元数据,符号引用进行验证。 准备:为类变量分配初始空间以及默认初始值,即零值。这里不会为实例变量分配,类变量分配在方法区中,实例变量跟随对象分配在堆中,final修饰的在编译期间就分配了,在准备阶段会显式的初始化。 解析:将常量池内的直接引用转为直接引用的过程。 链接过程完成之后开始初始化的过程: 初始化阶段就是执行类构造器方法的过程。此方法不需要定义,一个类只会被加载一次,虚拟机必须保证在多线程条件下类的构造方法是被加锁的。

epoll原理

epoll是一种高效地IO多路复用技术。调用epoll_create()会创建一个结构体数据,里面包含一个用于遍历扫描文件描述符状态的红黑树和一个就绪列表。调用epoll_ctr()可以进行增删改要监听的文件描述符及事件。调用epoll_wt()就会让内核检测就绪事件,将就绪事件到该表列表返回。epoll有两种触发机制。水平触发:当文件描述符状态改变时就立即进行IO操作,如果不进行处理将继续通知。边沿触发:是高速工作方式。该机制默认你已经知道了状态描述符改变,再你改变IO状态后描述状态改变后不会通知。该种方式减少了epoll的重复触发次数,提升了效率。必须使用非阻塞接口防止因一个文件描述符阻塞读写其他任务饿死。

请你说一下抽象类和接口的区别

1.抽象类多用于在同类事物中有无法具体描述的方法的场景,而接口多用于不同类之间,定义不同类之间的通信规则。

2.接口只有定义,而抽象类可以有定义和实现。

3.接口需要实现implement,抽象类只能被继承extends,一个类可以实现多个接口,但一个类只能继承一个抽象类。

4.抽象类倾向于充当公共类的角色,当功能需要累积时,用抽象类;接口被运用于实现比较常用的功能,功能不需要累积时,用接口。

请你说说==与equals()的区别

== 比较基本数据类型时,比较的是两个数值是否相等; 比较引用类型是,比较的是对象的内存地址是否相等。

equals() 没有重写时,Object默认以==来实现,即比较两个对象的内存地址是否相等; 重写以后,按照对象的内容进行比较

说说synchronize的用法及原理

一、用法:1. 静态方法上,则锁是当前类的Class对象。 2. 作用在普通方法上,则锁是当前的实例(this)。 3. 作用在代码块上,则需要在关键字后面的小括号里,显式指定一个对象作为锁对象。 能够保证同一个时刻只有一个线程执行该段代码,保证线程安全。 在执行完或者出现异常时自动释放锁。 二、原理:底层是采用Java对象头来存储锁信息的,并且还支持锁升级。在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步

说说你对AQS的理解

1、AQS队列同步器,用来构建锁的基础框架,Lock实现类都是基于AQS实现的。 2、AQS是基于模板方法模式进行设计的,所以锁的实现需要继承AQS并重写它指定的方法。 3、AQS内部定义了一个FIFO的队列来实现线程的同步,同时还定义了同步状态来记录锁的信息。 4、AQS的模板方法,将管理同步状态的逻辑提炼出来形成标准流程,这些方法主要包括:独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态。

Java哪些地方使用了CAS

1、CAS 比较并交换,比较典型的使用场景有原子类、AQS、并发容器。 2、AQS:在向同步队列的尾部追加节点时,它首先会以CAS的方式尝试一次,如果失败则进入自旋状态,并反复以CAS的方式进行尝试。 3、并发容器:以ConcurrentHashMap为例,它的内部多次使用了CAS操作。在初始化数组时,以CAS的方式修改初始化状态,避免多个线程同时进行初始化。在执行put方法初始化头节点时,它会以CAS的方式将初始化好的头节点设置到指定槽的首位,避免多个线程同时设置头节点。

说说JVM的垃圾回收算法

1.引用计数法,每次赋值时均要维护引用计数器且计数器本身也有一定的消耗,较难处理循环引用,一般不采用这种方式; 2.复制算法,将内存分为两块,每次只使用其中一块,当这块内存用完,就将还活着的对象复制到另外一块上面,效率高且没有碎片,但是需要双倍的空间,年轻代中使用复制算法; 3.标记-清除,先标记要清除的对象,然后统一回收这些对象,不需要额外的空间,但是需要两次扫描耗时严重并且会产生内存碎片; 4.标记-整理,标记存活对象,然后将标记的存活对象按内存地址依次排序,清除边界外未标记的对象,没有内存碎片,但是需要移动对象。老年代一般用标记-清除和标记-整理的混合实现。

请你说说Redis数据类型中的zset,它和set有什么区别?底层是怎么实现的?

zset是有序的,而set是无序的。 zset底层使用的是压缩列表以及跳跃表,当元素数量小于128个,所有member的长度都小于64字节,是使用压缩列表。不满足这两个条件时使用跳跃表。 set底层是hashtable和inset.

说说static修饰符的用法

static 修饰方法表示静态方法,修饰变量表示静态变量,不可以修饰局部变量。同时也可以表示静态代码块。

说说线程的状态

1:新建态:当一个线程被创建成功后,但并没有执行它的start方时处于该状态。

2:就绪态:一个线程执行了start方法进入就绪态开始竞争cpu调度权但还没有竞争到以完成它的任务

3:运行态:一个线程对象获取到了cpu的资源调度权,并进入允许态开始完成它的任务。

4:阻塞态:若一个运行中的线程存在同步操作,此时锁被其他线程占用,该线程就会进入阻塞态等待获取锁

5:限期等待:正在运行的线程执行了Thread.sleep()方法或者设置了timeout的wait()方法,join方法等进入一定时间的等待,系统自动唤醒。

6:不限期等待:正在运行的线程执行了未设置timeout的wait方法或join方法进入等待,只有通过其他线程使用interrupt()或notify方法对其进行唤醒。

7:死亡态:线程成功执行完毕或执行中抛出异常中断了线程会进入死亡态。

说说你对ThreadLocal的理解

ThreadLocal即线程变量,它用于共享变量在多线程中的隔绝,即每个线程都有一个该变量的副本彼此互不影响也就不需要同步机制了,实现原理:每个Thread对象中都有一个ThreadLocal类的内部类ThreadLocalMap对象,他是一个键值形式的容器,以ThreadLocal对象的get和set方法来存取共享变量值,原理时:以ThreadLocal对象作为key来存取共享变量值。一个ThreadLocal用完后必须remove,否则会造成内存泄漏。

说说Spring Boot常用的注解

@SpringBootApplication:它是SpringBoot的核心注解,用于开启自动配置,准确的说是通过该注解内的@EnableAutoConfiguration注解实现的自动配置。

@EnableAutoConfiguration:自动配置注解,在启动Spring应用程序上下文时进行自动配置,自动配置通常是基于项目classpath中引入的类和已定义的bean来实现的。

@Import:@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。

@Congiguration:配置类注解,根据一些特定条件来控制bean的实例化的行为。

@ComponentScan:位置在SpringBoot的启动类上,Spring包扫描。

说说Bean的生命周期

创建,初始化,调用,销毁;

具体流程如下:1,spring启动,查找并加载所需的Bean,然后初始化。2,进行Bean的属性依赖注入。3,如果Bean实现了BeanNameAware接口(@Resource,@Qualifier),spring会将Bean的Id传入SetBeanName方法去执行。4,如果Bean实现了BeanFactoryAware接口,spring会调用Bean的setBeanFactory方法将BeanFactory的ioc容器传入。5,如果Bean实现的时ApplicationContextAware接口的话,Spring会调用Bean的setApplicationContext将Bean应用的上下文传入进来。6,还一些其他的设定例如使用@PostContruct来定义Bean在初始化时执行的方法,或者使用@PreDestory来定义Ioc容器被销毁时执行的方法等。

synchronized和Lock有什么区别

synchronized是同步锁,可以修饰静态方法、普通方法和代码块。修饰静态方法时锁住的是类对象,修饰普通方法时锁住的是实例对象。当一个线程获取锁时,其他线程想要访问当前资源只能等当前线程释放锁。

synchronized是java的关键字,Lock是一个接口。 synchronized可以作用在代码块和方法上,Lock只能用在代码里。

synchronized在代码执行完或出现异常时会自动释放锁,Locl不会自动释放,需要在finally中释放。

synchronized会导致线程拿不到锁一直等待,Lock可以设置获取锁失败的超时时间。 synchronized无法获知是否获取锁成功,Lock则可以通过tryLock判断是否加锁成功。

说说volatile的用法及原理

1.保证可见性,当一个线程修改了呗volatile修饰的变量时,其他线程是可以看到的。 2.禁止指令重排序。

修饰被不同线程访问和修改的变量

被其修饰的变量,系统每次用到它时都是直接从对应的内存中提取,而不会利用缓存(如寄存器),所有线程在任何时候所看到变量的值都是相同的

volatile不能保证操作的原子性,因此不能代替synchronized 4.能不使用就尽量不要使用

说说Redis的单线程架构

redis的网络IO和键值对读写是单线程的,而其他功能如持久化、异步删除等是依赖其他线程来执行的。事实上他底层并不是单线程的。 1.对于服务端程序来说,线程切换和锁通常很影响性能,redis采用单线程则避免了线程间的阿切换和锁,减小了消耗。 2.redis的大部分操作实在内存上的完成的,因此它的性能很高。 3.redis采用IO多路复用机制,使其能够处理并发请求。

如何实现Redis高可用

主要有哨兵模式和集群模式这两种方式。

哨兵模式:哨兵模式是一个分布式架构,它包含若干个哨兵节点和数据节点,每一个哨兵节点都监控着其他的数据节点和哨兵节点,当发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它就会与其他哨兵节点协商,可以避免误判,当大多数哨兵节点都认为主节点不可达时,它们便会选择出一个哨兵节点来做自动故障转移工作,可以将从节点晋升为主节点,同时还会实时的通知到应用方,整个过程自动的,实现高可用。

集群模式:Redis集群采用虚拟槽分区来实现数据分片,它把所有的键根据哈希函数映射到0-16383整数槽内,计算公式为slot=CRC16(key)&16383,每一个节点负责维护一部分槽以及槽所映射的键值数据。

请你说一下final关键字

1.final被用来修饰类和类的成分。

2.final属性:变量引用不可变,但对象内部内容可变;被final修饰的变量必须被初始化。

3.final方法:该方法不能被重写,但子类可以使用该方法。

4.final参数:参数在方法内部不允许被修改

5.final类:该类不能被继承,所有方法不能被重写,但未被声明为final的成员变量可以改变。

请你说说重载和重写的区别,构造方法能不能重写

重载发生在同一个类中,要求方法名必须相同,参数列表不同,重载的发生于方法的返回值和访问修饰符无关,最终是生成了两个方法。

重写是发生在父子类中的,子类重写方法的名称和参数列表必须和父类相同,子类的返回值类型或抛出的异常类型必须是父类的返回值或异常类型及其子类型。子类重写方法的访问修饰符必须大于父类。若父类方法的修饰符为private则子类不能重写该方法。

构造方法不能重写。因为构造方法需要和类保持同名,而重写的要求是子类方法要和父类方法保持同名。如果允许重写构造方法的话,那么子类中将会存在与类名不同的构造方法,这与构造方法的要求是矛盾的。

请说说你对Java集合的了解

java中的集合类主要都有Collection和Map这两个接口派生而出,其中Collection又派生出List,Set,Queue。

所有的集合类都是List,set,queue,map这四个接口的实现类。其中,list代表有序的,可重复的数据集合;set代表无序的,不可重复的数据集合,queue代表先进先出的队列;map是具有映射关系的集合。最常用的实现类有ArrayList, LinkedList, HashMap, TreeMap, HashSet, TreeSet, ArrayQueue。

请你说说IO多路复用

IO多路复用指的是单个线程能够同时完成对多个IO事件的监听处理。linux提供了select、poll和epoll三种多路复用方式。本质上是利用内核缓存fd描述文件,并内核完成对文件描述符的监听操作。selec是将所用文件描述符的集合从用户态拷贝到内核空间,底层采用的是数组。poll和select相似,主要区别是poll底层使用的是链表,所以其能够监听的文件描述符不受限制。但是这两种方法都需要多次的内核与用户空间的复制拷贝,并且用户空间还需要在O(N)的时间复杂度下对描述符进行遍历才具体知道哪一个文件描述符发生了事件。epoll在内核开辟空间底层采用红黑树,用户可以直接在内核创建需要需要关注的文件描述的节点,当事件发送内核将对应文件描述符直接存入队列并将其返回到用户空间。epoll这种方式可以减少每次调用时从用户空间复制到内核的操作,并且因为内核返回的发送事件描述符的队列,可以减少每次轮询的操作,使得在O(1)的时间复杂度就能找到发送事件的描述符。

IO多路复用:单个线程同时操作多个IO请求。

select调用:查询有多少个文件描述符需要进行IO操作,特点:轮询次数多,内存开销大,支持文件描述符的个数有限。

poll调用:和select几乎差不多。但是它的底层数据结构为链表,所以支持文件描述符的个数无上限。

epoll:更加高效的调用方式,底层的数据结构为红黑树加链表。避免大内存分配和轮询。

请你说说索引怎么实现的B+树,为什么选这个数据结构?

1.索引本质上是通过预排序加树形结构来加快检索效率,为减少磁盘IO次数,在二叉树基础上将树改成多叉并加入一些限制条件,形成B树;B+树中所有的叶子节点存储了全部关键字集合;B+树为所有叶子节点增加了连接,从而实现快速范围查找。

2.在B+树中,所有记录都是按照键值的大小顺序放在同一层的叶子节点上,由叶子节点指针进行连接,在数据库中,B+树的高度一般在2-4层,也就是查找某键值记录最多需要2-4次IO。

请你讲一下Java 8的新特性

1、Lambda表达式:可将功能视为方法参数,或者将代码视为数据。使用 Lambda 表达式,可以更简洁地表示单方法接口(称为功能接口)的实例。 - 2、方法引用:提供了非常有用的语法,可直接引用已有Java类或对象(实例)的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。 - 3、对接口进行了改进:允许在接口中定义默认方法,默认方法必须使用default修饰。 - 4、Stream API:新添加的Stream API(java.util.stream)支持对元素流进行函数式操作。Stream API 集成在 Collections API 中,可以对集合进行批量操作,例如顺序或并行的 map-reduce 转换。 - 5、Date Time API:加强对日期与时间的处理。

请你说说泛型、泛型擦除

泛型的本质:参数化类型,即给类型指定一个参数。泛型:泛型接口,泛型类,泛型方法。泛型的好处:1.可以在编译时检查类型安全。2.所有的强制转换都是自动和隐式的,可以提高代码的重用率。

泛型擦除:编译器在编译期间将我们写好的泛型进行擦除,并相应的做出一些类型转换。

说说你了解的线程通信方式

在Java中提供了两种多线程通信方式分别是利用monitor实现通信方式和使用condition实现线程通信方式。

使用不同的线程同步方式也就相应的使用不同的线程通信方式。当我们使用synchronize同步时就会使用monitor来实现线程通信,这里的monitor其实就是锁对象,其利用object的wait,notify,notifyAll等方法来实现线程通信。

而使用Lock进行同步时就是使用Condition来实现线程通信,Condition对象通过Lock创建出来依赖于Lock对象,使用其await,sign或signAll方法实现线程通信。

请你说说JUC

JUC是java.util.concurrent的缩写,这个包中包含了支持并发操作的各种工具。

1.原子类:遵循比较和替换原则。可以用于解决单个变量的线程安全问题。

2.锁:与Synchronized类似,在包含synchronized所有功能的基础上,还支持超时机制,响应中断机制,主要用于解决多个变量的线程安全问题。

3.线程池:可以更方便的管理线程,同时避免重复开线程和杀线程带来的消耗,效率高。

4.并发容器:例如ConcurrentHashMap,支持多线程操作的并发集合,效率更快。

请你说说HashMap和Hashtable的区别

1.两者的父类不同,HashMap的父类是AbstractMap类,HashTable的父类是Dictionary类

2.HashMap和HashTable都实现了Map,Serializable,Cloneable这三个接口

3.两者的线程安全性不同,HashMap是线程不安全的,在多线程并发的环境下会产生死锁的问题 HashTable是线程安全的,因为它底层的方法都有Synchronized修饰 虽然HashMap是线程不安全的,但是他的执行效率要比HashTable要高很多 在多线程并发的环境下推荐使用ConcurrentHashMap,因为它底层采用了分段锁,并不对整个进程进行绑定

4.对Null值的支持不同,HashMap允许Null Key 和Null Value,但是为了确保key的唯一性,只允许一个key为null,但是允许多个Value为Null 而HashTable Key和Value都不能为Null ,如果为Null的话会直接报空指针异常

5.HashMap在JDK1.7以前底层是基于数组加链表实现的,在1.7以后是采用数组加链表加红黑树实现的

6.HashMap初始容量大小为16,每次扩容为2n,HashTable初始容量为11,每次扩容为2n+1

HashMap是线程安全的吗?如果不是该如何解决?

不是线程安全,底层实现是"数组、链表、红黑树",在多线程put时可能会造成数据覆盖,并且put会执行modCount++操作,这步操作分为读取、增加、保存,不是一个原子性操作。解决办法就是不要在多线程中使用HashMap,或者使用更安全的CurrentHashMap,CurrentHashMap通过对桶加锁,以较小的性能来保证线程安全。

请你说说Java的四种引用方式

java中的四种引用方式分别是:

1,强引用,以new关键字创建的引用都是强引用,被强引用引用的对象永远都不会被回收。

2,软引用:以SoftRererenc引用对象,被弱引用引用的对象只有在内存空间不足时会被垃圾回收。

3,弱引用,以WeakReference引用对象,被弱引用引用的对象一定会被回收,它只能存活到下一次垃圾回收。

4,虚引用:以PhantomReference引用对象,一个对象被引用引用后不会有任何影响,也无法通过该引用来获取该对象,只是其再被垃圾回收时会收到一个系统通知。

请你讲下G1垃圾回收器

G1(Garbage First)是一种现代化的垃圾回收器。相比于传统的标记-清除和标记-整理垃圾回收器,G1垃圾回收器的主要优势在于能够在可控的停顿时间内高效地处理大量内存数据。

G1垃圾回收器的工作过程主要分为以下几个阶段:

  1. 初始标记阶段(Initial Marking Phase):在这个阶段中,G1垃圾回收器会标记出所有的根对象,并且标记它们的直接引用对象。这个阶段需要停止应用程序的运行,并且通常只需要几百毫秒的时间完成。

  2. 并发标记阶段(Concurrent Marking Phase):在这个阶段中,G1垃圾回收器会并发地扫描堆中的所有存活对象,并标记它们。这个阶段不需要停止应用程序的运行,因此可以与应用程序同时进行。

  3. 最终标记阶段(Final Marking Phase):在这个阶段中,G1垃圾回收器会停止应用程序的运行,并且标记出在并发标记阶段中产生的新的存活对象。这个阶段通常需要几十毫秒的时间。

  4. 筛选阶段(Live Data Counting and Evacuation):在这个阶段中,G1垃圾回收器会根据堆中存活对象的大小和分布情况,选择一些区域作为回收的目标,并将这些区域中的存活对象复制到新的区域中。这个阶段需要停止应用程序的运行,但通常只需要几百毫秒的时间。

G1垃圾回收器的主要特点包括以下几个方面:

  1. 垃圾回收时长可预测:由于G1垃圾回收器可以控制垃圾回收的时间,因此可以预测垃圾回收的时间并进行优化。这使得应用程序可以在可控的时间内响应用户请求。

  2. 高效利用内存:G1垃圾回收器采用了分代回收的思想,同时将堆内存划分成大小相等的区域,因此可以更加有效地利用内存。

  3. 最小化停顿时间:G1垃圾回收器采用了多线程和并发处理的技术,使得垃圾回收时应用程序的停顿时间最小化。

请你说说内存溢出

内存溢出:指程序运行过程中申请的内存大于系统能够提供的内存,导致无法申请到足够的内存,于是就发生了内存溢出。引起内存溢出的原因:1.内存加载的数据量过于庞大。如一次从数据库取出的过多的数据。

2.代码中存在死循环或者死循环中产生大量的对象实体。

3.启动内存值设定过小。解决内存溢出的方案:1.修改JVM启动参数,直接增加内存。2.检查错误日志,查看“OutOfMemory”错误之前是否存在异常。3.对代码进行debug分析。4.使用内存工具动态查看内存使用情况。

常见的内存溢出出现在:1.堆。对象创建过多2.栈溢出3.方法区和运行时常量池;创建大量动态类。

请你说说内存泄漏

Java中的内存泄漏指的是程序中无用的对象占用了内存空间,导致内存不足的情况。当程序运行时,Java虚拟机会自动管理内存的分配和回收。如果程序中存在内存泄漏,则可能导致内存空间的浪费和不足,最终可能会导致应用程序的崩溃或性能下降。

Java中的内存泄漏通常是由于以下原因引起的:

  1. 对象无法被回收:在Java中,当一个对象不再被引用时,Java虚拟机会自动回收它所占用的内存空间。但是,如果这个对象的引用仍然存在,即使这个对象已经不再使用,Java虚拟机也无法回收它所占用的内存空间。这种情况下就会出现内存泄漏。

  2. 对象缓存未清理:在一些程序中,为了提高性能,会将一些对象进行缓存。但是,如果这些缓存的对象一直存在,却不再被使用,就会占用大量内存空间,导致内存泄漏。

  3. 长时间运行的任务:如果程序中存在长时间运行的任务,而这些任务所使用的对象占用内存空间过多,就会导致内存泄漏。

  4. 单例模式的使用不当:在使用单例模式时,如果实例化的对象一直存在,却不再被使用,就会导致内存泄漏。

避免Java中的内存泄漏,可以采取以下措施:

  1. 及时清理对象的引用:在Java中,当对象不再被引用时,可以手动将其引用清空,这样Java虚拟机就可以回收这个对象占用的内存空间。

  2. 适时清理缓存:如果程序中存在缓存对象的情况,应该及时清理缓存中不再使用的对象,以释放内存空间。

  3. 使用合适的数据结构:对于长时间运行的任务,可以使用合适的数据结构来存储数据,以避免内存泄漏。

  4. 单例模式使用注意:在使用单例模式时,应该考虑对象是否需要一直存在,如果不需要,可以手动将其清理。同时,应该考虑使用弱引用或软引用等技术来实现单例模式,以避免内存泄漏。

请你说说数据库引擎有哪些,各自有什么区别

1.InnoDB引擎支持MySQL事务,具有提交,回滚和崩溃恢复功能能够更加安全的保护用户数据;支持行级锁,提高多用户并发和性能;支持外键,维护数据完整性。

2.MyISAM引擎,占用空间较小,支持表级锁,能够限制读写工作的负载的性能,查询效率较高,常用于只读场景。

3.Memory引擎,将所有数据存储在RAM(主存)中,在非关键字查询时,查询效率较高。

简单介绍Spring

1.Spring框架包含众多模块,如Core,Testing,Data Access,Web Servlet等,其中Core是整个Spring框架的核心模块。

2.Core模块提供了IOC容器,AOP功能,数据绑定,类型转换等一系列基础功能,其中这些功能以及其他模块的功能都是在建立在IOC和AOP之上,因此IOC和AOP是Spring框架的核心。

3.IOC 意为控制反转,是一种面向对象编程的设计思想,如果在不采用这种设计思想情况下,我们需要自己维护对象与对象之间的依赖关系,这就很可能导致对象之间的耦合性较高;在采用了IOC容器后,我们可以让IOC容器去维护对象与对象之间的依赖关系,从而能够降低对象之间的耦合度。

4.IOC是通过DI(依赖注入)实现的,实现依赖注入的关键就是IOC容器,其本质上是一个工厂。

5.AOP是面向切面编程的思想,该思想是对OOP的补充,可以在OOP的基础上进一步提高编程的效率,其可以统一解决一批组件的共性需求(权限检查,记录日志,事务管理等),在AOP思想下,我们可以将解决共性需求的代码独立出来,然后通过配置的方式,声明这些代码在什么地方,什么时候调用,当满足了调用条件后,AOP会将该业务代码织入到我们指定的位置,从而统一的解决问题,有不需要我们去修改代码。

介绍一下MyBatis的缓存机制

MyBatis的缓存机制是为了提高数据库操作的性能而设计的。它可以在查询相同数据时避免重复查询数据库,提高查询效率。MyBatis提供了两级缓存机制:一级缓存和二级缓存。

一级缓存是指在同一个会话中,多次执行相同的查询语句时,MyBatis会把第一次查询的结果缓存下来,下次执行相同查询时就直接从缓存中获取结果,而不需要再次查询数据库。一级缓存的作用域是会话级别的,当会话结束时,一级缓存也会被清空。

二级缓存是指多个会话之间共享缓存,当多个会话都查询相同的数据时,MyBatis会把第一次查询的结果缓存到一个共享的缓存区域中,其他会话再查询相同的数据时,就可以直接从缓存中获取结果,而不需要再次查询数据库。二级缓存的作用域是Mapper级别的,需要在Mapper配置文件中进行配置。

需要注意的是,缓存虽然可以提高查询性能,但也会带来一些问题。如果查询的数据经常被更新,那么缓存中的数据就会变得不准确,可能会导致脏数据的问题。因此,在使用MyBatis缓存时,需要根据具体情况进行配置和管理。

请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()

1、hashCode():获取哈希码,equals():比较两个对象是否相等。 2、二者两个约定:如果两个对象相等,它们必须有相同的哈希码;若两个对象的哈希码相同,他们却不一定相等。也就是说,equals()比较两个对象相等时hashCode()一定相等,hashCode()相等的两个对象equqls()不一定相等。 3、加分回答:由于hashCode()与equals()具有联动关系,equals()重写时,hashCode()进行重写,使得这两个方法始终满足相关的约定。

说说线程的创建方式

创建线程有三种方式,分别是继承Thread类,实现Runnable接口,实现Callable接口。

继承Thread类之后我们需要重写run()方法,方法中是我们希望这个线程执行什么操作,再创建对象的实例,通过实例对象的start()方法开启这个线程。

实现runnable接口之后,我们需要实现run方法,方法中同样写我们需要执行的操作,然后将实现了接口的类作为参数创建一个Thread对象,通过这个对象的start方法开启线程。

实现Callable之后,需要实现call方法,方法中写我们需要的操作,然后创建实现接口类的对象,将对象作为参数创建FurtureTask对象,再将task对象作为参数创建thread对象,调用start方法开启线程,还可以使用task对象的get方法获取返回值。他们的区别是前二者不能获取返回值,callable接口可以获得返回值,一般在实际使用中,更多使用实现接口的方式开启线程,因为接口不会占用类的继承位置。

说说你对ArrayList的理解

ArrayList 底层是用Object数组实现的,通过默认构造器创建容器时,首先会初始化为一个空数组,然后在第一次添加元素时创建大小为10的数组,超出限制会增加50%的容量,并且数据以System.arraycopy()复制到新数组,当然也可以指定初始容量。如果一次添加多个元素,如addAll,如果添加元素的个数大于默认扩容的长度,则会使用两者中最大的作为扩容后的容量

请你说说BIO、NIO

unix提供了五种IO模型:阻塞IO,非阻塞IO,IO多路复用,信号驱动IO,异步IO。

BIO:对应的是阻塞IO,一次只能操作一个IO。

NIO是IO多路复用,一个线程可以同时处理多个IO请求。

IO多路复用模型提供select,poll,epoll调用。select调用的主要作用就是统计有多少个文件描述符处于就绪状态,但是支持的文件描述符的个数有限。poll调用和select几乎一样,但是其底层数据结构为链表,支持的文件描述符的个数无上限。epoll是更高效的调用,底层数据结构为红黑树和链表,避免了轮询和大量内存的分配。

说说你对Spring Boot的理解,以及它和Spring的区别?

1、从本质上来说,Spring Boot就是Spring,它帮你完成了一些Spring Bean配置。 2、Spring Boot使用“习惯优于配置”的理念让你的项目快速地运行起来 3、但Spring Boot本身不提供Spring的核心功能,而是作为Spring的脚手架框架,达到快速构建项目的目的 Spring Boot优点, 可以快速构建项目 - 可以对主流开发框架的无配置集成 - 项目可独立运行,无需外部依赖Servlet容器 - 提供运行时的应用监控 - 可以极大地提高开发、部署效率 - 可以与云计算天然集成 核心功能: 1.自动配置 针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置。 2.起步依赖 Spring Boot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依赖和Gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖。 3.端点监控 Spring Boot 可以对正在运行的项目提供监控。

说说Spring Boot的自动装配

靠EnableAutoConfigurations注解,首先会从spring.factories中寻找有没有AutoConfiguration类满足Conditional注解的生效条件,有的话,就是实例化该AutoConfiguration类,然后加载到spring容器就实现了spring的自动装配

说说@Autowired和@Resource注解的区别

@Autowied是Spring提供的注解,@Resource是JDK提供的注解。

@Autowied是只能按类型注入,@Resource默认按名称注入,也支持按类型注入。@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用

说说Redis的主从同步机制

主从同步分为全量同步和增量同步,从机第一次连接主机时不会携带主机id和数据偏移量,主机会对从机的主机id进行校验,如果不是则说明是第一次连接需要进行全量同步,原理就是将当前数据写到RDB文件发送给从机,从机接收到文件之后将数据读取到从机的内存中,增量同步是第二次和之后连接才会发生,当从机第一次同步完成之后,主机在这期间数据发生变化,会将命令存储在缓冲区,当校验到从机的id正确时会获取从机的偏移量,主机从偏移量记录的命令开始将从机没同步的数据的操作命令发送给从机执行,执行完成后即完成了数据同步

说说Redis的缓存淘汰策略

惰性删除:客户端访问一个key的时候,redis先检查它的过期时间,如果已经过期了就立刻删除这个key

定期删除:redis会将设置了过期时间的key保存到一个字典里面,然后每过十秒就扫描一次。这个定期删除也不扫描字典中所有的key,而是采用了一种简单的弹性策略。 定期删除对内存更加友好,而惰性删除对CPU更加友好,所以redis采用的是定期删除+惰性/懒汉式删除。

说说垃圾收集器

Serial(新生代)、Serial Old(老年代):适用于单核小CPU,单核工作,回收时会暂停其他工作stop the word。

PawNew(新生代)、CMS(老年代):适用于多核CPU,最求短暂停时间,多核工作,使用标记清除算法,最短的暂停时间。

Parallel Scavenge(新生代-标记复制算法)、Parallel Old(老年代-标记整理算法):1.7,1.8默认的组合,适用于多核CPU,追求最大吞吐量

G1 jdk1.9默认,适用于大内存多核CPU服务器,它不按整个新生代或老年代去回收,而是开辟了面向局部收集,实现了较小的收集暂停时间和高吞吐量。

请你说说Java的特点和优点,为什么要选择Java?

  1. java语言的特点是:一次编译,到处运行,即平台无关性;是纯面向对象的语言。

  1. JAVA语言的优点有:内置的类库简化了开发人员的设计工作;具有较好的安全性和健壮性;开发人员不需要对内存的使用进行管理。

  1. 选择JAVA的原因是:使用范围很广,安卓操作系统的应用软件目前大部分还是使用JAVA语言编写。

介绍一下包装类的自动拆装箱与自动装箱

装箱是基本数据类型转成包装类,,拆箱是包装类转基本数据类型。其实就是拆包装和包装。

说说wait()和sleep()的区别

  1. 所属的类型不同 - wt()是Object类的实例方法,调用该方法的线程将进入WTING状态。 - sleep()是Thread类的静态方法,调用该方法的线程将进入TIMED_WTING状态。

  2. 对锁的依赖不同 - wt()依赖于synchronized锁,通过监视器进行调用,调用后线程会释放锁。 - sleep()不依赖于任何锁,所以在调用后它也不会释放锁。

  3. 返回的条件不同 - 调用wt()进入等待状态的线程,需要由notify()/notifyAll()唤醒,从而返回。 - 调用sleep()进入超时等待的线程,需要在超时时间到达后自动返回。

说说你对线程池的理解

线程池可以有效的管理线程:管理线程数量、线程复用、 线程池的生命周期包含5个状态: RUNNING、SHUTDOWN、STOP、TIDING、TERMINATED。这5种状态的状态值分别是:-1、0、1、2、3。在线程池的生命周期中,它的状态只能由小到大迁移,是不可逆的。

简单说下你对JVM的了解

jvm是java语言跨平台的关键,是java的虚拟机,jvm由三部分组成:类加载器,运行时数据区,执行引擎。

当程序运行时,会把对象、方法、返回值、局部变量等放入运行时数据区,运行时数据区由方法区、堆、本地方法栈、虚拟机栈、程序计数器组成,其中方法区跟堆是线程共享的,虚拟机栈跟程序计数器是线程私有的.

说说Java运行时数据区

  1. 程序计数器: - 程序计数器是一块较小的内存空间 - 它可以看作是当前线程所执行的字节码的行号指示器。虚拟机就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成 - 每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

  2. 虚拟机栈: - 虚拟机栈也是线程私有的,它的生命周期与线程相同。 - 虚拟机栈描述的是Java方法执行的线程内存模型: - 每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。 - 每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 - 在Java虚拟机规范中,对这个区域规定了两类异常状况: - 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常。 - 如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。

  3. 本地方法栈: - 本地方法栈则是为虚拟机使用到的本地方法服务。 Java虚拟机规范对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机直接就把本地方法栈和虚拟机栈合二为一。 与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

  4. 堆: - 堆是虚拟机所管理的内存中最大的一块。 - Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。 - 此内存区域的唯一目的就是存放对象实例 - 堆是垃圾收集器管理的内存区域。 - 堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的。 - 如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,JVM将会抛出OutOfMemoryError异常。

  5. 方法区: - 方法区是各个线程共享的内存区域 - 用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 - 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生成的各种字面量与符号引用。 - 如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。

请你讲下CMS垃圾回收器

CMS垃圾收集器采用标记清除算法,使用多线程实现,所以它的应用场景一般为服务端系统的老年代。它是一个以达到在垃圾回收期间用户线程低停顿为目标的垃圾收集器。CMS垃圾收集器垃圾回收分为四个阶段:

1,初始标记:只对与GCRoots有直接关键的对象进行可达性分析的标记。

2,并发标记:标记整个GCRoots引用链中的对象,与用户线程并发执行。

3,重新标记:用于更新在并发标记过程中被复活的对象。

4,并发清除:清除标记阶段判断的已死亡的对象。该流程与用户线程并发执行。

缺点:1,它使用标记清除算***导致内存碎片化,2,会产生在并发清除阶段的浮动垃圾,只有到下一次垃圾回收时才会被清除。

说说JVM的双亲委派模型

双亲委派模型的工作过程是,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。

请你说说数据库索引的底层数据结构

索引可选的底层数据结构有:二叉树、红黑树、hash、B+树,

但mysql索引的底层用的并不是二叉树和红黑树。因为二叉树和红黑树在某些场景下都会暴露出一些缺陷。首先,二叉树在某些场景下会退化成链表,而链表的查找需要从头部开始遍历,这就失去了加索引的意义。

不适用红黑树的原因是:红黑树作为底层数据结构在面对某些表数据动辄数百万数千万的场景时,会导致索引树的高度很高。索引从根节点开始查找,而如果我们需要查找的数据在底层的叶子结点上,那么树的高度多少,就要进行多少次查找,数据存在磁盘上,访问需要进行磁盘IO,这回导致效率过低。

而B+树由B树和索引顺序访问方法演化而来,它是为磁盘或其他直接存取辅助设备设计的一种平衡查找树,在B+树中,所有记录点都是按键值的大小顺序存放在同一层的叶子节点,各叶子节点通过很进行链接。B+树索引在数据库中的一个特点就是高扇出行。

说说Spring Boot的启动流程

run方法调用,创建springApplication实例化对象,再这个对象调用自身的run方法,获取监听器参数配置=》打印banner信息=》创建初始化容器=》监听器消息推送

介绍一下Spring MVC的执行流程

  1. 用户点击某个请求路径,发起一个 HTTP request 请求,该请求会被提交到 DispatcherServlet(前端控制器);

  2. 由 DispatcherServlet 请求一个或多个 HandlerMapping(处理器映射器),并返回一个执行链(HandlerExecutionChain)。

  3. DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter(处理器适配器);

  4. HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);

  5. Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);

  6. HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;

  7. DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;

  8. ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;

  9. DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);

  10. 视图负责将结果显示到浏览器(客户端)。

在MyBatis中$和#有什么区别

在MyBatis中$和#的两种占位符,使用$设置参数时,MyBatis会创建普通的SQL语句,执行SQL时,直接将参数拼接在SQL中,可能会产生SQL注入攻击,但在某些场景中,比如需要动态指定SQL语句中的列名时,就只能使用$占位符了. 使用#设置参数时,MyBatis会创建预编译的SQL语句,预编译的SQL语句执行效率高,并且可以防止SQL注入攻击,在实际开发中,大部分情况下使用#占位符

请你说说Java基本数据类型和引用类型

1.提供8种基本数据类型:byte(8), short(16), int(32), long(64), float(32), double(64), char(16), boolean,这些基本数据类型有对应的封装类;这基本数据类型在声明之后就会立刻在栈上被分配内存空间。

2.其他类型都是引用类型:类,接口,数组,String等,这些变量在声明时不会被分配内存空间,只是存储了一个内存地址。

请你说说Java的异常处理机制

1、异常处理机制让程序具有容错性和健壮性,程序运行出现状况时,系统会生成一个Exception对象来通知程序 2、处理异常的语句由try、catch、finally三部分组成。try块用于包裹业务代码,catch块用于捕获并处理某个类型的异常,finally块则用于回收资源。 3、如果业务代码发生异常,系统创建一个异常对象,并将其提交给JVM,由JVM寻找可以处理这个异常的catch块,并将异常对象交给这个catch块处理。如果JVM没有找到,运行环境终止,Java程序退出。 4、Java也允许程序主动抛出异常。当业务代码中,判断某项错误的条件成立时,可以使用throw关键字向外抛出异常。

说说你对面向对象的理解

1、面向对象三大基本特征:封装、继承、多态。 2、封装:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,让外部程序通过该类提供的方法来实现对内部信息的操作和访问,提高了代码的可维护性; 3、继承:实现代码复用的重要手段,通过extends实现类的继承,实现继承的类被称为子类,被继承的类称为父类; 4、多态的实现离不开继承,在设计程序时,我们可以将参数的类型定义为父类型。在调用程序时根据实际情况,传入该父类型的某个子类型的实例,这样就实现了多态。

请介绍一下访问修饰符

private、default、protected、public; 被访问修饰符的成员访问

private只能在当前类的内部被访问,default可以被相同包下的其它类访问、protected可以被同一个包下的访问、public无论访问类和被访问类在不在一个包下,有没有父子关系,这类成员都基于被访问到

说说Java中常用的锁及原理

synchronized关键字和lock锁接口:synchronized关键字底层采用java对象头来存储锁信息的。lock锁接口是基于AQS实现的, 而Lock接口的经典实现是ReentrantLock。AQS内部定义一个先进先出的队列实现锁的同步,同时还定义了同步状态来记录锁信息。

请你说说List与Set的区别

List和Set都是Collection接口的子接口,它们的主要区别在于元素的有序性和重复性:

List代表有序的元素可以重复的集合,集合中每个元素都有对应的顺序索引,它默认按元素的添加顺序设置元素的索引,并且可以通过索引来访问指定位置的集合元素。另外,List允许使用重复元素。 Set代表无序的元素不可重复的集合,它通常不能记住元素的添加顺序,但是也可以是有序的支持排序的实现类。Treeset,底层由Treemap实现

请你讲一下Java NIO

NIO弥补了原来同步阻塞IO的不足,它在标准Java代码中提供了高速的、面向块的IO。通过定义包含数据的类,以及通过以块的形式处理这些数据。

NIO包含三个核心的组件:Buffer(缓冲区)、Channel(通道)、Selector(多路复用器)。Buffer是一个对象,它包含一些写入或者要读出的数据。在读取数据时,它是直接读到缓冲区中的。在写入数据时,写入到缓冲区中,任何时候访问NIO中的数据,都是通过缓冲区进行操作。Channel是一个通道,可以通过它的读取和写入数据,它就像自来水管一样,网络数据通过Channel读取和写入。通道和流的不同之处在于通道是双向的,流只是在一个方向上移动而且通道可以用于读、写或者同时读写。Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有心的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的IO操作。

说说GC的可达性分析

可达性分析算法用于判断对象是否可以被回收,程序通过GC Roots中的对象为起点,以类之间的引用关系简历引用链,最终形成一个类似于数据结构中森林的一个结果,不存在与森林中的对象便是需要被回收的对象。

这里的GC Roots主要包括线程栈中引用的变量,本地方法栈中引用的变量,方法区中的静态引用对象,常量池中的常量引用对象和被锁引用的对象。对一个对象真正的宣告回收需要经历两次标记过程,如果一个对象不再引用链上就会对它进行第一次标记,并判断它是否重新了finalize方法,若未重新或finalize方法已经被执行过了则会直接回收对象,否则会创建一个F-queue队列来存储这些对象,并启动一个低优先级的Finalizer线程去执行它们的finalize方法。第二次标记,稍后收集器会对队列中的对象进行可达性分析并标记,若仍然存在标记则表明该对象没有通过finalize方法实现自救则直接回收,否则对象复活。任何对象的finalize方法都只能被调用一次。

说说类的实例化过程

类加载--分配内存--初始化0值--状态设置--构造函数

请你讲讲B树和B+树

B树和B+树都是平衡多路查找树,B+树中,数据存放在叶子节点中,叶子节点之间通过指针链表连接起来,而B树中,数据存放在叶子节点和非叶子节点中。在查找过程中,B+树比B树更加稳定,所有的数据形成链表适合范围查找,而且单一节点存储更多的元素是,使得IO的次数更少。

MySQL主从同步是如何实现的?

复制(replication)是MySQL数据库提供的一种高可用高性能的解决方案,一般用来建立大型的应用。总体来说,replication的工作原理分为以下3个步骤:

  1. 主服务器(master)把数据更改记录到二进制日志(binlog)中。

  2. 从服务器(slave)把主服务器的二进制日志复制到自己的中继日志(relay log)中。

  3. 从服务器重做中继日志中的日志,把更改应用到自己的数据库上,以达到数据的最终一致性

请你介绍一下数据库的ACID

原子性:事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。回滚可以用回滚日志(Undo Log)来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

一致性:数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。

隔离性:一个事务所做的修改在最终提交以前,对其他事务是不可见的。

持久性:一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。

系统发生崩溃可以用重做日志(Redo Log)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改。

请你说说数据库的索引是什么结构,为什么不用哈希表

MySQL中的索引B+树实现的; 哈希表的查询效率的确最高,时间复杂度O(1),但是它要求将所有数据载入内存,而数据库存储的数据量级可能会非常大,全部载入内存基本上是不可能实现的; B+树可以分段加载需要的节点数据,可以在内存资源有限的前提下,极大提高查询效率。

请你说说InnoDB的MVCC

MVCC为多版本并发控制,指的就是使用读已提交和可重复读这两种隔离级别的事务在执行普通select操作的时候访记录的版本链的过程,这样子可以使不同的事务读写并发操作,从而提高系统性能。 这两个隔离级别的一个很大不同就是生成readview的时机不同,读已提交在每一次进行普通select操作前就会生成一个readview,而可重复读只在第一次进行普通select的时候生成一个readview,数据的可重复读其实就是read view的重复使用。

说说Spring Boot的起步依赖

starter配置,约定大于配置,spring boot将日常企业应用研发中的各种场景都抽取出来,做成一个个的starter(启动器),starter中整合了该场景下各种可能用带的依赖,用户只需要在Maven中引入starter依赖,spring boot就能自动扫描到要加载的信息并启动响应的默认配置

说说Spring事务管理

spring支持编程式事务管理和声明式事务管理两种方式: ①编程式事务管理使用TransactionTemplate。

②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。 声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

说说Bean的作用域,以及默认的作用域

singleton 默认作用域,单例bean,每个容器中只有一个bean的实例。 prototype 为每一个bean请求创建一个实例。 session 同个会话共享一个 request 一次请求创建一个 global-session 全局作用域,所有会话共享一个实例。

说说BeanFactory和FactoryBean的区别

beanfactory:是所有spring bean容器的顶级接口,是最基础的IOC容器,它为spring的容器定义了一套方法,并提供像getBean这样的方法从容器中获取指定的bean实例。它在产生bean的同时,还提供了解决bean之间的依赖注入的能力,也就是所谓的di。

factory bean:工厂bean,只是SpringIOC容器创建Bean的一种形式,它是一个接口,它的主要功能是动态去生成某一类型的bean的一个实例,也就是说 我们可以自定义一个bean并且加载到ioc容器里面,getObject是用来实现动态构建bean的一个过程。

说说你对Redis的了解

Redis是一款基于键值对的NoSQL数据库,Redis中拥有string(字符串),hash(哈希)、list(列表)、set(集合)等多种数据结构,redis将数据写进内存的性能很快,不仅如此,如遇到系统崩溃,内存中的数据不会丢失;redis访问速度快、支持的数据类型丰富,很适合用来储存热点数据、 而且适用业务广,如可以运用expire命令来做限时业务,设置一个键的生存时间,到时间后redis会自动删除它,如排行榜可以借住redis的SortedSet进行热点数据的排序,还有分页查询,模糊查询,点赞好友等

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值