java
- 请说说多线程?
线程是程序执行的最小单元,一个进程可以拥有多个线程;各个线程之间共享程序的内存空间和系统分配的资源,但是各个线程拥有自己的栈空间;多线程的优点可以减少程序的响应时间,提高CPU利用率。 - 怎样保证线程安全?
线程安全问题是指在多线程背景下,线程没有按照我们的预期执行,导致操作共享变量出现异常。在Java中有三种方式:原子类、volatile关键字、锁。 - 说说你对反射的理解?
反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它的所有的方法和属性,对于任意一个对象,都能知道他的属性和方法。获取Class对象的三种方式:getClass();xx.class;Class.forName("xxx");反射的优点:运行期间能够动态的获取类,提高代码的灵活性。缺点:性能比直接的Java代码要慢很多。应用场景:spring的xml配置模式,以及动态代理模式都用到了反射。 - ArrayList和LinkedList的区别?
ArrayList底层是数组实现的,数组是一组连续的内存单元,读取快(使用索引),插入删除慢(需要重新计算大小或是更新索引)LinkedList底层基于双向链表,读取慢,插入删除快。 - 你知道哪些线程安全的集合?
java.util包中的集合类大部分都是非线程安全的,例如:ArrayList/LinkedList/HashMap等等,但也有少部分是线程安全的,像是Vector和Hashtable,它们属于很古老的API了,是基于Synchronized实现的,性能很差,在实际的开发中不常用。一般可以使用Collections工具类中的synchronizedXxx()方法将非线程安全的集合包装成线程安全的类。在java5之后可以使用concurrent包提供的大量的支持并发访问的集合类,例如ConcurrentHashMap/copyOnWriteArrayList等。 - String、StringBuffer、StringBuilder有什么区别?
StringBuilder和StringBuffer非常类似,均代表可变的字符序列,而且方法也一样。String是不可变字符序列,效率低,但是复用率高;StringBuffer是可变字符序列、效率较高(增删)、线程安全;StringBuilder是可变字符序列、效率最高、线程不安全。 - 说说HashMap的底层原理?
HashMap的底层是数组+链表+红黑树实现的。集合put时,通过计算key键的哈希值来放入元素。若有key值相同的哈希值时,会通过链表进行存放,链表长度达到8时会开辟红黑树进行存放,以此提高查询效率。 - 说说你了解的JVM内存模型?
JVM由三部分组成:类加载子系统、执行引擎、运行时数据区。类加载子系统:可以根据指定的全限定名来载入类或接口;执行引擎:负责执行哪些包含在被载入类的方法中的指令;运行时数据区:分为方法区、堆、虚拟机栈、本地方法栈、程序计数器,当程序运行时,JVM需要内存来存储许多内容,例如:字节码、对象、参数、返回值、局部变量、运行时的中间结果等,把这些东西都存储到运行时数据区中,以便于管理。 - 说说JVM的垃圾回收机制?
垃圾回收通常有三件事情要做。第一将决定哪些对象要回收,jvm决定了将未被引用的对象占用空间回收,通常用引用计数法或者可达性分析法,后者时jvm主流使用方法;第二是什么时候回收jvm,通常在内存不够用时,或定期回收;第三用什么方式回收,有分代回收,分区域回收法,收集器有年轻代收集器、老年代收集、混合收集、整堆收集。 - 说说JVM的垃圾回收算法?
引用计数法:每次赋值时均要维护引用计数器本身也有一定的消耗,较难处理循环引用,一般不采用这种方式;复制算法:将内存分为两块,每次只使用其中一块,当这块内存用完,就将还活着的队形复制到另一块上面,效率高且没有碎片,但是需要双倍的空间,年轻代中使用复制算法;标记清除:先标记要清除的对象,然后统一回收这些对象,不需要额外的空间,但还是需要两次扫描,耗时严重并且会产生内存碎片;标记整理:标记存活对象,然后将标记的存货对象按内存地址依次排序,清除边界外未标记的对象,没有内存碎片,但是需要移动对象。老年代一般有标记-清除和标记-整理的混合实现。 - 说说类加载机制?
类加载的过程中首先判断这个是否被加载过,如果没有被加载过,那么调用类加载器进行加载,判断这个类是否符合规范,如果不符合就抛出异常,加载成功就会成class对象。接下来是链接过程,分为三步:验证、准备、解析。验证:确保文件符合规范,不会危害虚拟机自身的安全,对文件格式,字节码,元数据,符号引用进行验证;准备:为类变量分配初始空间以及默认初始值,即零值,这里不会为实例变量分配,类变量分配在方法区中,实例变量跟随对象分配在堆中,final修饰的在编译期间就分配了,在准备阶段会显示的初始化;解析:将常量池内的符号引用转为直接引用的过程。链接过程完成之后开始初始化的过程:初始化阶段就是执行类构造方法的过程,此方法不需要定义,一个类只会被加载一次,虚拟机必须保证在多线程条件下类的构造方法是被加锁的。 - 说一下抽象类和接口的区别?
抽象类多用于在同类事物中有无法具体描述的方法的场景,而接口多用于不同类之间,定义不同类之间的通信规则;接口只有定义,而抽象类可以有定义和实现;接口需要实现,抽象类只能被继承,一个类可以实现多个接口,但一个类只能继承一个抽象类;抽象类倾向于充当公共类的角色,当功能需要累计时,用抽象类;接口被运用于实现比较常用的功能,功能不需要累计时,用接口。 - 请说说==与equals()的区别?
==比较基本数据类型时,比较的时两个数值是否相等;比较引用类型时,比较的是对象的内存地址是否相等。equals()没有重写时,Object默认以==来实现,即比较两个对象内存地址是否相等;重写以后对象的内容进行比较。 - 说说synchronize的用法及原理?
用法:1.静态方法上,则锁的是当前类的Class对象;2.作用在普通方法上,则锁的是当前的实例(this);3.作用在代码块上,则需要在关键字后面的小括号里,显示指定一个对象作为锁对象。能够保证同一个时刻只有一个线程执行该段代码,保证线程安全,在执行完成或者出现异常时自动释放锁。原理:底层是采用Java对象头来存储锁信息的,并且还支持锁升级。在jvm里的实现都是基于进入和退出Monitor对象来实现方法同步和代码块同步。 - 说说你对AQS的理解?
AQS队列同步器,用来构建锁的基础框架,Lock实现类都是基于AQS实现的。它的原理就是维护一个共享资源,然后利用队列让线程队列获取资源的一个过程。内部实现的关键是队列和state状态,。它有两种线程模式:独占和共享模式。 - java哪些地方使用了CAS?
CAS的全程是CompareAndSwap直译为比较和交换。它是普遍处理器都支持的一条指令,这条指令通过判断当前内存值V,旧的预期值A,即将更新的值B是否相等来对比并设置新值,从而实现变量的原子性。CAS比较典型的使用场景有原子类、AQS、并发容器。 - 说说static修饰符的用法?
static修饰变量:属于静态变量也叫类变量,置属于类对象而不是实例,可以通过类名访问,它一般会在类加载过程中被初始化,生命周期贯穿整个程序,存储在方法区中;static修饰方法:即静态方法,一个类中的静态方法不能访问该类的实例变量,只能访问静态变量,同时还存在一个静态初始化块,他在类加载过程中被调用用于对该类中的静态变量进行操作;static修饰类:即静态内部类,他只能以内部类的形式存在,可以通过外部类的类名调用,它是也只能访问到外部的静态成员。 - 说说线程的状态?
线程的状态有6种。1.初始:线程被创建但是还没有调用start()方法;2.运行(就绪和运行):线程正在执行,但也可能在等待操作系统的调度;3.阻塞:若一个运行中的线程存在同步操作,此时锁被其他线程占用,该线程就会进入阻塞态等待获取锁;4.限期等待:正在运行的线程执行了Thread.sleep()方法或者设置了timeout的wait()方法,join方法等进入一定时间的等待,系统自动唤醒;5.不限期等待:正在运行的线程执行了未设置timeout的wait方法或join方法进入等待,只有通过其他线程使用interrupt()或notify方法对其进行唤醒;6.死亡态:线程成功执行完毕或执行中抛出异常中断了线程会进入死亡态。 - 说说你对ThreadLocal的理解?
ThreadLocal即线程变量,它将需要并发访问的资源复制多份,让每个线程拥有一份资源,由于每个线程都拥有自己的资源副本,从而也就没有必要对该变量进行同步了。实现原理:每个Thread对象中都有一个ThreadLocal类的内部类ThreadLocalMap对象,他是一个键值形式的容器,以ThreadLocal对象的get和set方法来存取共享变量值,一个ThreadLocal用完后必须remove,否则会造成内存泄露。 - synchronized和Lock有什么区别?
1.synchronized比Lock锁接口出现要早;2.synchronized即可以修饰静态方法,也可以修饰实例方法以及代码块;3.synchronized的实现方式是基于JVM的,外人不能更改,而lock锁接口是外部API;4.synchronized支持的功能少于lock锁接口,lock锁接口在支持synchronized原有功能的基础上,还支持超时机制和响应中断等功能;5.synchronized底层采用java对象头来存储锁信息,lock实现类是基于AQS实现的,早期synchronized的性能很差,只有”无锁“和”有锁“两种状态,但后期synchronized引入了锁升级机制,有了很大改善,锁现在有四种状态:无锁、偏向锁、轻量级锁、重量级锁,减少了获取锁和释放锁带来的性能消耗。 - 说说volatile的用法及原理?
volatile主要用于修饰多线程中的共享变量,它可以保证变量的可见性和有序性,不能保证原子性。可见性是指在多线程环境中若某个线程修改了该变量,那么其他线程就能察觉到变量的修改。可见性主要是通过在内存模型中修改了某值则将它同步回主内存,在读取前从主内存刷新该变量值来实现的。有序性是指在某个线程中对该变量的操作顺序是透明的,不会改变的。在java中由于寄存器和内存处理速度存在巨大差异所以java为了提升运行速度会将编译好的代码进行一定程度的重排,但不影响其在独立线程环境下的运行结果,但是在多线程环境中有可能出错。volatile可以避免这种指令重排实现其在多线程的顺序一致的效果。 - 说一下final关键字?
final被用来修饰类和类的成分。final属性:变量引用不可变,但对象内部内容可变,被final修饰的变量必须被初始化;final方法:该方法不能被重写,但子类可以使用该方法;final参数:参数在方法内部不允许被修改;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三种多路复用方式。select调用:查询有多少个文件描述符需要进行IO操作,特点:轮询次数多,内存开销大,支持文件描述符的个数有限;poll调用:和select几乎差不多,但是它的底层数据结构为链表,所以支持文件描述符的个数无上限;epoll:更加高效的调用方式,底层的数据结构为红黑树加链表,避免大内存分配和轮询。 - 请你讲一下java8的新特性?
1.Lambda表达式:可将功能视为方法参数,或者将代码视为数据,使用Lambda表达式可以简洁地表示单方法接口的实例;2.方法引用:提供了非常有用的语法,可直接引用已有Java类或对象的方法或构造器。与lambda联合使用,方法引用可以使语言的构造结构更紧凑简洁,减少冗余代码;3.对接口进行了改进:允许在接口中定义默认方法,默认方法必须使用default修饰;4.Stream API:新添加的Stream API支持对元素流进行函数式操作;5.Date Time API:加强对日期时间的处理。6.Optional类:用来解决空指针异常。 - 请你说说泛型、泛型擦除?
泛型:java在jdk1.5时引入了泛型,在没有泛型之前,每次从集合中读取的对象都必须进行类型转换,如果在插入对象时,类型出错,那么在运行时转换处理的阶段就会出错,在提出泛型之后就可以明确的指定集合接受哪些对象类型,编译器就能知晓并且自动为插入的代码进行泛化,在编译阶段告知是否插入类型错误的对象,程序会变得更加安全清晰。泛型擦除:java泛型是伪泛型,因为java代码在编译阶段,所有的泛型信息会被擦除,java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码文件中不包含泛型信息的,使用泛型的时候加上的类型,在编译阶段会被擦除掉,这个过程成为泛型擦除。 - 说说你了解的线程通信方式?
在java中提供了两种多线程通信方式分别是利用monitor和condition两种。具体使用那种通信方式与线程同步方式有关。对于synchronized来说,使用的是monitor的同步方式。常用的方法有wait(),notify(),notifyAll()。对于lock锁接口来说,使用的是condition,依赖于lock锁的创建而创建,常使用的方法有await(),signal(),signalAll()。 - 说说JUC?
JUC是java.util.concurrent的缩写,这个包中包含了支持并发操作的各种工具。1.原子类:遵循比较和替换原则。可以用于解决单个变量的线程安全问题;2.锁:与synchronized类似,在包含synchronized所有功能的基础上,还支持超时机制,响应中断机制,主要用于解决多个变量的线程安全问题;3.线程池:可以更方便的管理线程,同时避免重复开线程和杀线程带来的消耗,效率高;4.并发容器:例如ConcurrentHashMap,支持多线程操作的并发集合,效率更快。 - 说说HashMap和Hashtable的区别?
Hashtable在实现Map接口时保证了线程安全性,而HashMap则时非线程安全的,所以Hashtable的性能不如HashMap,因为为了保证线程它牺牲了一些性能。Hashtable不允许存入null,无论是以null作为key或value,都会引发异常但HashMap是允许的。Hashtable是很古老的api,性能不好,不推荐使用,要在多线程下使用ConcurrentHashMap,它不但保证了线程安全,也通过降低锁的粒度提高了并发访问时的性能。 - 说说java的四种引用方式?
1.强引用:以new关键字创建的引用都是强引用,被强引用的对象永远都不会被回收;2.软引用:以SoftRerference引用对象,被软引用的对象只有在内存不足时会被垃圾回收;3.弱引用:以WeakReference引用对象,被弱引用的对象一定会被回收,它只能存活到下一次垃圾回收;4.虚引用:以PhantomReference引用对象,一个对象被引用后不会有任何影响,也无法通过该引用来获取该对象,知识其再被垃圾回收时会收到一个系统通知。 - 说说你对ArrayList的理解?
arrayList在jdk1.7的时候,创建容器的时候会在底层创建一个长度为10的object数组,在jdk1.8的时候,在创建容器的时候底层并不会立刻创建,只有在第一次调用add方法的时候才会创建一个长度为10的数组,默认情况下,扩容为原来容量的1.5倍,同时将原有数组中的值复制到新的数组中,并且arraylist属于有序的,可重复的集合,提供了iterator方法,增强了迭代能力。 - 说说BIO、NIO、AIO?
unix提供了五种IO模型:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO、异步IO。BIO对应的时阻塞IO,一次只能操作一个IO;NIO是IO多路复用,一个线程可以同时处理多个IO请求,IO多路复用模型提供select、poll、epoll调用;AIO:也是基于epoll,特点是有效的请求才启动线程,现有操作系统完成再通知服务端。 - 说说垃圾收集器?
Serial(新生代)、Serial Old(老年代):适用于单核小CPU,单核工作,回收时会暂停其他工作stop the world;PawNew(新生代)、CMS(老年代):适用于多核CPU,追求短暂时间,多核工作,使用标记清除算法,最短的暂停时间;Parallel Scavenge(新生代-标记复制算法)、Parallel Old(老年代-标记整理算法):1.7,1.8默认的组合,适用于多核CPU,追求最大吞吐量;G1 jdk1.9默认,使用于大内存多核CPU服务器,它不按整个新生代或老年代去回收,而是开辟了面向局部收集,实现了较小的收集暂停时间和高吞吐量。 - 说说java的特点和优点,为什么要选择java?
1.吸收了c++的优点,摒弃了c++多继承和指针的复杂使用,不需要对内存进行释放,具有垃圾回收机制;2.面向对象,易于开发和理解;3.跨平台,因为jvm,同一个代码可以再不同的平台机器上运行,.java文件通过编译成.class字节码文件再由jvm转成机器能识别的机器码;4.内含大量的库,简化编写工作;5.使用于开发web. - 介绍一下包装类的自动拆装箱与自动装箱?
自动装箱、自动拆箱时JDK1.5提供的功能;自动装箱:把一个基本类型的数据直接赋值给对应的包装类型;自动拆箱是指把一个包装类型的对象直接赋值给对应的基本类型;通过自动装箱、自动拆箱功能简化基本类型变量和包装类对象之间的转换过程。 - 说说wait()和sleep()的区别?
sleep方法是当前线程的休眠,让出cpu,但是不释放锁,它是Thread的静态方法,wait是当前线程等待,释放锁,是Object的方法;sleep没有释放锁,wait释放锁,两者都可暂停线程的执行,wait用于线程间的通信,sleep用于暂停线程。wait等待过程不占用CPU,sleep仍然占用。wait调用后,需要别的线程调用同一个对象的notify方法。 - 说说你对线程池的理解?
线程池可以有效的管理线程:1.它可以管理线程的数量,可以避免无节制的创建线程,导致超出系统负荷直至崩溃;2.它可以让线程复用,可以大大地减少创建和销毁线程所带来地开销。线程池需要依赖一些参数来控制任务地执行流程,其中最重要地参数有:corePoolSize核心线程池数、workQueue等待队列、maxinumPoolSize最大线程池数、handler拒绝策略、keepAliveTime空闲线程存活时间。当我们想线程池提交一个任务之后,线程池按照如下步骤处理这个任务:1.判断线程数是否达到corePoolSize,若没有则新建线程执行该任务,否则进入下一步;2.判断等待队列是否已慢,若没有则将任务放入等待队列,否则进入下一步;3.判断线程数是否达到maxinumPoolSize,如果没有则新建线程任务,否则进入下一步;4.采用初始化线程池时指定地拒绝策略,拒绝执行该任务;5.新的线程处理完当前任务后,不会立即关闭,而是继续处理等待队列中地任务。如果线程地空闲时间达到了keepAliceTime,则线程池会销毁一部分线程,将线程地数量收缩至corePoolSize。第二步中地队列可以有界也可以无界,若指定无界队列,则线程池永远无法进入第三步,相当于废弃了MaxinumPoolSizecabs参数。这种做法是十分危险的,如果任务在队列中产生大量的堆积,就很容易造成内存泄露。JDK为我们提供了一个名为Executors的线程池的创建工具,该工具创建出来的就是带有无界队列的线程池,所以一般在工作中我们不建议使用这个类来创建线程池。 - 说说JVM的双亲委派模型?
双亲委派模型的工作过程是,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。双亲委派模型最大的优点是可以避免类的覆盖。 - 说说java基本数据类型和引用类型?
8种基本数据类型的变量的值存放在栈内存,有char(16)、byte(8)、short(16)、int(32)、long(64)、float(32)、double(64)、boolean。引用类型的变量存放引用地址,堆内存才是真正的值。类,接口,数组,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无论访问类和被访问类在不在一个包下,有没有父子关系,这类成员都基本被访问到。 - 说说java中常用的锁及原理?
通过synchronized关键字和Lock接口两种方式实现加锁。1.synchronized关键字通过monitor(同步监视器)来实现锁的获取和释放,每一个monitor相当于一个有序队列,底层通过java对象头的方式存储锁的信息;2.通过实现Lock接口,创建一个local,显式指定锁对象,每一个锁对象是一个condition,通过condition来实现线程同步,可以有多个,每个condition就是一个有序队列,相比synchronized的Lock更加灵活,Lock底层是通过AQS抽象同步队列实现有序队列,同时还定义乐同步状态state来记录锁的信息。 - 说说List与Set的区别?
List和Set都是Collection接口的子接口,它们的主要区别在于元素的有序性和重复性:List代表有序的元素可以重复的集合,集合中每个元素都有对应的顺序索引,它默认按元素的添加顺序设置元素的索引,并且可以通过索引来访问指定位置的集合元素,另外,List允许使用重复元素;Set代表无序的元素不可重复的集合,它通常不能记住元素的添加顺序。 - 请讲一下java nio?
nio弥补了原来同步阻塞io的不足,它在标准的java代码中提供了高速的、面向块的io。nio有三个核心组件,分别是Buffer、Channel、Selector。Buffer:缓冲区,读写数据都要进过缓冲区,而不是经过流;Channel:通道,全双工双向通道,用来读取和写入数据的;Selector:多路复用器,不断的轮询注册在Selector上的Channel,如果有新的TCP接入,就会被轮询出来,Channel变成就绪状态,SelectionKey获取Channel的集合,进行后续io操作。JDK使用epoll(),没有了最大连接句柄1024/2048的限制,一个线程负责Selector的轮询,就能接入成千上万的客户端。
spring
- 说说你对MVC的理解?
MVC是一种设计模式,在这种模式下软件被分为了三层,model模型层、view试图层、controller控制层,模型层主要是定义实体对象等数据,视图层主要是用户界面等,控制层主要是具体的业务逻辑相关负责请求的分发、把模型数据及时的反映在页面上,三者之间各司其职,降低了业务之间的耦合度。spring MVC框架就是MVC设计模式的最好实现。 - 说说你对AOP的理解?
AOP是面向切面编程,它是一种编程思想,它是通过一种预编译方式和运行期间动态代理的方式实现不修改源码的情况下给程序动态添加功能的一种技术,可以降低代码的耦合度,便于管理,提高代码的可重用性。AOP的实现方式有两种:JDK动态代理,可以在运行时创建接口的代理实例。CGLib动态代理:可以在运行期间创建子类的动态实例。AOP的应用场景有:事务,日志管理等。 - 说说你对IOC的理解?
控制反转。控制:对象的创建的控制权限;反转:将对象的控制权限交给spring。之前我们创建对象时用new,现在直接从spring容器中取,维护对象之间的依赖关系,降低对象之间的耦合度。实现方式为依赖注入,有三种注入方式:构造器、setter、接口注入。 - 说说spring boot常用的注解?
@SpringBootApplication是springBoot的核心注解,用于开启自动配置,准确的说是通过注解内的@EnableAutoConfiguration注解实现的自动配置;@EnableAutoConfiguration自动配置注解,在启动Spring应用上下文时进行自动配置,自动配置通常时基于项目classpath中引入的类和已定义的bean来实现的;@Configuration配置类注解,根据一些特定条件来控制bean的实例化的行为;@ComponentScan:位置在springBoot的启动类上,spring包扫描。 - 说说Bean的作用域,以及默认作用域?
Bean有五种不同的作用域singleton、prototype、session、request、golbal session,默认情况下,Bean在spring容器中是单例的,但是我们可以通过@Scope注解来修改Bean的作用域。1.singleton:在spring容器中仅存在一个实例,即bean以单例形式存在;2.prototype:每次调用getBean()的时候,会new一个新的实例并返回;3.Session:同一个Http下的session共享一个Bean,不同HTTP下不同的session使用不同的Bean;4.request:每一次HTTP请求都会创建一个新的Bean;5.globalSession:同一个全局Session会共享一个Bean。 - 说说Bean的生命周期?
创建、初始化、调用、销毁;bean的创建方式有四种,构造器、静态工厂、实例工厂、setter注入的方式。spring在调用bean的时候因为作用域的不同,不同的Bean初始化和创建的时间也不同。在作用域为singleton的时候,bean是随着容器一起被创建好并且实例化的,在作用域为prototype的时候,bean是随着它被调用的时候才创建和实例化完成。然后程序就可以使用bean了,当程序完成销毁的时候,bean也被销毁。 - 简单介绍一下spring?
spring是一个轻量级企业应用开发框架,它有两大核心功能,分别是ioc和aop,ioc控制反转是将创建对象的权限交给spring容器来进行管理,可以很好的起到解耦的作用,aop是一种编程思想,底层使用的是动态代理,可以在程序原有的功能上进行增强,常用的地方有日志记录,权限验证等。 - 说说你对spring boot的理解,以及它和spring的区别?
从本质上来说,springboot就是spring,它帮你完成了一些springbean配置;springboot使用习惯优于配置的理念让你的项目快速运行起来;但springboot本身不提供spring的核心功能,而是作为spring的脚手架框架快速达到构建项目的目的;springboot的优点,可以快速构建项目,可以对主流开发框架的无配置集成,项目可独立运行,无需外部依赖servlet容器,提供运行时的应用监控,可以极大的提高开发和部署效率,可以与云计算天然集成;核心功能:1.自动配置:针对很多spring应用程序常见的应用功能,springboot能自动提供相关配置。2.起步依赖:springboot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的maven依赖和gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖。3.端点监控Lspringboot可以对正在运行的项目提供监控。 - 说说springboot的自动装配?
springboot自动装配时,需要引入相应的starter,启动springboot后会自动配置相应的依赖和配置相应的初始化参数,以最便捷,最简单的方式完成第三方软件的集成。具体的配置过程是:springboot通过@EnableAutoConfiguration开启自动配置功能,加载spring.factories中注册的AutoConfiguration类,当某个AutoConfiguration类满足指定的生效条件时,实例化该AutoConfiguration中定义的Bean,并注入到spring容器中,完成依赖框架的自动配置。 - 说说springboot的启动流程?
当springboot项目完成创建后会默认生成一个Application的入口类,该类中的main方法可以启动springboot项目,在main方法中,通过springApplication的静态方法,即run方法进行springApplication的实例化操作,然后针对该实例化对象调用另一个run方法去完成整个项目的初始化和启动;SpringApplication的run方法的中重要操作:获取监听器的参数配置,打印Banner信息,创建并初始化容器,监听器发送通知。 - 介绍一个spring MVC的执行流程?
1.用户向服务器发送请求,请求被spring的前端控制Servlet DispatcherServlet捕获;2.DispatcherServlet对请求URL进行解析,得到请求资源标识符URI,然后根据该URI调用HandlerMapping获得该Handler配置的所有相关的对象,最后以HandlerExecutionChain对象的形式返回;3.DispatcherServlet根据获得的Handler,选择一个合适的HandlerAdapter,提取request中的模型数据,填充handler入参,开始执行handler(controller),Handler执行完成后,向DispatcherServlet返回一个ModelAndVuew对象;4.DispatcherServlet根据返回的ModelAndView选择一个合适的ViewResolver;5.通过ViewResolver结合Model和View来渲染视图,DispatcherServlet将渲染结果返回给客户端。 - 说说spring的事务管理?
spring为事务提供了一致的模板,支持两种事务编程模型:1.编程式事务:spring提供了TransactionTemplate模板,利用模板我们可以通过编程的方式实现事务管理,而无需关心资源获取,服务,释放事务以及异常处理,编程式事务用法麻烦,但是可以事务管理的范围控制的更加精准;2.使命式事务:声明式事务用法十分方便,我们只需在类或方法上加上@Transactional注解便可以声明事务性,它是spring事务管理的亮点,在IOC配置中,指定事务的边界和事务属性,spring会自动在特定的事务边界上应用事务特性。 - 说说@Autowired和@Resource注解的区别?
@Autowired是spring提供的注解,而@Resource是JDK提供的注解。@Autowired默认按类型装配,默认情况下必须要求依赖对象存在,如果要允许null值,可以设置它的required属性为false。如果想使用名称装配可以结合@Qualifier注解进行使用。@Resource默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行名称查找。 - mybatis中#和$有什么区别?
在MyBatis中$和#的两种展位符,使用$设置参数时,mybatis会创建普通的sql语句,执行sql时,直接将参数拼接在sql中,可能会产生sql注入攻击,但在某些场景中,比如需要动态指定sql语句中的列名时,就只能使用$占位符。使用#设置参数时,mybatis会创建预编译的sql语句,预编译的sql语句执行效率高,并且可以防止sql注入攻击,在实际开发中,大部分情况下使用#占位符。 - 介绍一下Mybatis的缓存机制?
mybatis的缓存机制:一级缓存称为本地缓存,它默认启动,存在于sqlSession的生命周期中,他会把方法和入参计算之后生成一个key来存储缓存。二级缓存可以在配置文件setting中手动配置,默认关闭,在使用的时候在Mapper.xml文件中也需要加上标签。二级缓存存在与SqlSessionFactory中,他可以实现多个sqlSession缓存的共享,以名称空间为单位存储,我们在开发的时候一班不使用二级缓存,因为它的弊大于利,例如在两个名称空间中都有对某个表的操作,一个名称空间改了,不会影响另一个的缓存,就会造成内存不一致。一般我们会使用redis来实现缓存。
数据库
- 请你说说mysql索引,以及它们的好处和坏处?
mysql索引是一种帮助快速查找数据的数据结构,可以把它理解为书的目录,通过索引能够快速找到数据所在位置。场景的索引数据结构有:Hash表,通过hash算法快速定位数据,但不适合范围查询,因为需要每个key都进行一次hash、二叉树,查找和修改效率都比较高,但是在InnoDB引擎中使用的索引是B+ Tree,相较于二叉树,B+ Tree这种多叉树,更加矮宽,更适合存储在磁盘中。使用索引增加了数据查找的效率,但是相对的由于索引也需要存储到磁盘,所以增加了存储的压力,并且新增数据时需要同步维护索引。但是合理的使用索引能够极大提高我们的效率。 - 请你讲讲B树和B+树?
B树和B+树都是平衡多路查找树,B+树中,数据存放在叶子节点中,其它节点存放key,而B树中,数据存放在叶子节点和非叶子节点中。1.由于B+树非叶子节点存储的是关键字,所以相对与b树来说一页就能容纳更多的关键字,b+树就会变得更宽更矮,从而读取关键字时就能减少io次数,提高了查询效率;2.由于b+树的叶子节点是通过链表链接的,并且是叶子节点有序的,所以在范围查询时b+树只需要查找到范围的下限然后遍历叶子节点就能得到整个范围的记录;3.b+树的查询效率更加稳定,因为每次查询所走的路径长度都相同,只需要从根节点查到叶子节点即可。 - 数据库为什么不用红黑树而使用B+树?
AVL树和红黑树基本都是存储在内存中才会使用的数据结构。而数据库中的数据的索引会非常大,索引为了减少内存的占用,索引会被存储到磁盘文件中,此时影响数据库查询效率的主要隐私就是磁盘的IO次数。AVL树和红黑树由于一个父节点只能存储两个子节点。所以使用AVL树或红黑树存储大规模数据时,树的深度就会很深,此时磁盘的IO次数也会大幅度增加。B+树中一个父节点有多个子节点,减少了树的深度,磁盘IO次数也相应的减少。 - 请你说说乐观锁和悲观锁?
乐观锁:乐观锁总是假设最好的情况,每次去拿数据的时候默认别人不会修改,所以不会上锁,只有当更新的时候会判断一个在此期间有没有更新了这个数据,适用于多读,可以使用版本号机制进行控制。悲观锁:悲观锁总是假设最坏的情况,每次去拿数据时都认为别人会修改,所以每次在拿数据时都会上锁,这个别人想拿这个数据时会阻塞直到拿到锁。mysql数据库的共享锁和排他锁都是悲观锁的实现。 - 请你说说mysql的事务隔离级别?
事务的隔离级别有:未提交读、提交读、可重复读、串行化四种隔离级别。数据库多个事务同时执行时可能造成事务的一致性问题:脏读,不可重复读,幻读;提交读可以解决脏读的问题:脏读就是线程A修改一个数据没有提交,线程B读取了这个修改的数据之后线程A回滚,线程B读取到的数据就变成了脏数据。可重复读可以解决脏读和不可重复度的问题,不可重复读就是事务A多次读取一个数据,事务B在A读取的过程中进行了修改,导致事务A多次读取到的数据不一致。串行化可以同时解决三个问题,幻读就是事务A对一个表进行读取,事务B在A读取的时候增减或者删除数据,导读事务A读取到的数据并不一致。 - 说说聚簇索引和非聚簇索引?
它们最大的区别是索引和数据是否放在一起。聚簇索引:索引和数据放在一起,叶子节点保留数据行。非聚簇索引:索引和数据分开存放,叶子节点存放的是指向数据行的地址。 - 请你说说mysql的数据库引擎有哪些,各自有什么区别?
1.InnoDB引擎是mysql的事务完全存储引擎,具备提交、回滚和崩溃恢复功能,支持行锁,增删改性能更优;2.MyISAM引擎支持全文索引,只支持表级锁,通常用于只读或以读为主的场景,表占用空间较小;3.Memory引擎是将所有数据都存储在RAM中,以便在需要快速查找非关键数据的环境中进行快速访问,以前被称为HEAP引擎;4.Archive引擎非常适合存储大量的独立的历史数据,拥有高效的插入速度,查询支持较差。 - mysql主从同步是如何实现的?
复制replication是mysql数据库提供的一种高可用高性能的解决方案,一般用来建立大型的应用。总的来说,replication的工作原理分为以下3个步骤:1.主服务器master把数据更改记录到二进制日志中binlog中;2.从服务器slave把主服务器的二进制日志复制到自己的中继日志relay log中;3.从服务器重做中继日志中的日志,把更改应用到自己的数据库上,以达到数据的最终一致性。 - 请你介绍以下数据库的ACID?
原子性A(atomicity):事务中的任何一个sql语句执行失败,那么整个事务都应该是失败的,已经执行成功的sql语句也应该回撤,数据库的状态也应该回到事务执行前的状态;一致性C(consistency):事务将数据库从一种状态转变为另一种状态,在转换完成后,数据的完整性约束并没有发生改变;隔离性I(isolation):每个事务读写操作的对象需要与其他事务读写操作事务对象相互分离,即该事务提交前对其他事务都不可见;持久性D(durability):事务一旦执行成功,那么结果是永久性的,哪怕系统发生错误,数据库也能将数据恢复。
redis
- 说说redis的数据结构?
redis有五种基本数据类型String、hash、list、set、zset和三种特殊数据类型geo、hyperloglog bitmap。String:底层是一个动态字符串,支持扩容,存储字符串;list:存储有序可以重复的元素,底层数据结构是双向链表/压缩链表;hash:存储的是键值对,底层是ziplist和hash;set:存储的不可重复元素,底层数据结构可以是hash和整数数组;zset:存储的是有序的不可重复的元素,底层是ziplist和链表。 - 说说redis的持久化策略?
redis的持久化方式有两种:RDB和AOF。RDB:它是通过数据集快照的方式来记录redis中的所有数据,在某个时间段内,将数据写入一个临时文件中,持久化结束,用这个临时文件替换上次的持久化文件,达到数据恢复。好处:只有一个dump.rdb文件,便于存储,容灾性较好,性能最大化,子进程来完成写操作,主进程继续处理命令。缺点:数据安全性低,RDB是隔一段时间进行一次备份,在此期间,如果发生了异常,可能导致数据的不完整性。AOF:它是通过记录redis的所有命令,每执行一次就记录一次数据,保存在AOF文件中。优点:保证了数据的安全性和完整性,即便是中途宕机,也可以恢复过来。缺点:他的文件比RDB文件大,如果是数据集大的时候,它的恢复速度比RDB文件慢。redis默认是用RDB文件存储。 - 如何利用redis实现一个分布式锁?
分布式环境下会发生多个server并发修改一条数据的情况。因此需要分布式锁。我们可以在redis中存放一份代表锁的数据。最简单的方式就是setnx key value但是如果客户端忘记解锁就会造成死锁。如果给锁增加过期时间,expire key second会发生另一个问题,因为这两步并非原子性操作,如果第二部失败仍然会出现死锁问题。因此可以通过set key value nx ex seconds操作将两步封装成原子操作,解锁就是将代表锁的数据删除即可。但不能简单的del key因为如果进程A在没有执行完毕时锁到期释放了,但进行A结束后仍然会释放锁,这时候就会释放其他进程的锁。所以要给key赋一个随机值代表进程加个标识,进程A释放锁时进行判断是自己的再释放锁。另外释放这部分要保证原子性否则会死锁。可以使用Lua脚本将指令编排到一起,Lua执行是原子性的。 - 说说缓存穿透、击穿、雪崩的区别?
缓存穿透:客户端访问redis中不存在的数据,是的请求直达存储层,导致负载过大甚至宕机,原因可能是业务层误删了缓存或是有人恶意访问不存在的数据。解决方式:1.存储层未命中后,返回空指存入存储层,客户端再次访问时,缓存层直接返回空值;2.将数据存入布隆过滤器,访问缓存之前经过滤器拦截,若请求的数据不存在则直接返回空值。缓存击穿:一份热点数据,它的访问量非常大,在它缓存失效的瞬间,大量请求直达存储层,导致服务崩溃。解决方案:1.永不过期,对热点数据不设置过期时间;2.加互斥锁,当一个线程访问该数据时,另一个线程只能等待,这个线程访问之后,缓存中的数据将被重建,届时其他线程就可以从缓存中取值。缓存雪崩:大量数据同时过期,或是redis节点故障导致服务不可用,缓存层无法提供服务,所有的请求直达存储层,造成数据库宕机。解决方案:1.避免数据同时过期,设置随机过期时间;2.启用降级和熔断措施;3.设置热点数据永不过期;4.采用redis集群,一个宕机,另外的还能用。 - redis如何与数据库保持双写一致性?
先更新数据库再删除缓存,缓存删除失败需要重试。 - 说说redis的单线程架构?
redis采用的是单线程+IO多路复用技术。这里的单线程指的是redis中读写操作和网路IO使用的是有一个线程来完成,但是其他操作是有其他线程完成,例如持久化操作。单线程既可以简化数据结构和算法的实现,同时也消除了线程切换和锁竞争所带来的消耗。redis中采用的IO多路复用技术实现了单线程下同时处理多个IO请求。redis为生命这么块:1.单线程进行读写操作,避免线程切换和锁竞争带来的消耗;2.redis操作是在内存中进行的;3.最重要的就是采用了IO多路复用技术,实现了在网路IO中能都处理大量并发请求,实现高吞吐率。 - 如何实现redis的高可用?
主从复制:写一定是在主服务器上,然后主服务器同步给从服务器。缺点:当主服务器挂掉的时候,不能自动切换到从服务器上。主从服务器存储数据一样,内存可用性差。优点:在一定程度上分担主服务器读的压力。哨兵模式:构建多个哨兵节点监视主从服务器,当主服务器挂掉的时候,自动将对应的从服务器切换成主服务器。优点:实现自动切换,可用性高。缺点:主从服务器存储数据一致,内存可用性差。还要额外维护一套哨兵系统,较为麻烦。集群模式:采用无中心节点的方式实现。多个主服务器相连,一个主服务器可以有多个从服务器,不同的主服务器存储不同的数据。优点:可用性更高,内存可用性高。 - 说说Redis的主从同步机制?
主从同步分为全量同步和增量同步,从机第一次连接主机时不会携带主机id和数据偏移量,主机会对从机的主机id进行校验,如果不是则说明是第一次连接需要进行全量同步,原理就是将当前数据写到RDB文件发送给从机,从机接收到文件之后将数据读取到从机的内存中,增量同步是第二次和之后连接才发生,当从机第一次同步完成之后,主机在这期间数据发生变化,会将命令存储在缓冲区,当校验到从机的id正确是会获取从机的偏移量,从机从偏移量记录的命令开始将从机没同步的数据的操作命令发送给从机执行,执行完成后即完成了数据同步。 - 说说redis的缓存淘汰策略?
惰性删除:客户端访问一个Key的时候,redis先检查它的过期时间,如果已经过期了就立刻删除这个key。定期删除:redis会将设置了过期时间的key保存到一个字典里面,然后每过十秒就扫描一次。这个定期删除也不扫描字典中所有的Key,而是采用了一种简单的弹性策略。定期删除对内存更加友好,而惰性删除对CPU更加友好,所以redis采用的是定期删除+惰性删除。