javq学习

1、面向对象:

面向对象相对应的就是面向过程,而面向过程就是注重事情的一个步骤和顺序,而面向对象注重的是参与者,而参与者里面每个对象负责干那些事情,面向过程比较直接高效,而面向过程更易于复用,扩展和维护
面向对象的特点:封装,继承,多态
封装:封装的意义在于明确标识允许外部使用的所有成员函数和数据项,内部细节对外部隐藏,外部调用无需修改或者关心内部实现
继承:继承基类的方法,并做出自己的改变和扩展,子类共性的方法或者属性直接使用父类的,而不需要自己在定义,只需要扩展自己个性化的
多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同

2、==和equals的区别:

==对于基本数据来说是比较他们的值,对于对象来说就是比较他们在堆中的地址,也就是比较他们是不是同一个实例,

//这个是str1的值是在栈里面的
String str1 = "Hello" 
//这是new出来了一个对象,数据保存在堆里面的
String str2 = new String("Hello") 
//这是通过的引用赋值,也就是str3和str2指向的同一个地址
String str3 = str2;

3、String,StringBuffer,StringBuilder的区别和使用场景:

String是使用final修饰的,每次操作都会产生新的String对象
StringBuffer和StringBuilder都是在原对象上面操作的
StringBuffer是线程安全的,StringBuilder是线程不安全的
StringBuffer方法都是Synchronized修饰的
性能:StringBuilder>StringBuffer>String
优先使用使用StringBuilder,多线程使用共享变量时使用StringBuffer

4、重载和重写的区别:

重载是发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时
重写:发生在父子类中,方法名,参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类,如果父类的方法访问修饰符为private则子类就不能重写该方法

5、接口和抽象类的区别:

  • 抽象类中可以存在普通成员函数,而接口类中只能存在public abstract方法
  • 抽象类中的成员变量可以实各种类型的,而接口类中的成员变量只能是public static final类型的
  • 抽象类只能继承一个,接口可以实现多个
    接口设计的目的是对类的行为进行约束,他只约束了行为的有无,弹不对具体实现进行限制,抽象类不允许实例化
    抽象类的设计目的是代码复用,当不同的类具有相同的行为,且其中实现方式一致时,可以让这些类派生出一个抽象类
    抽象类是对类本质的抽象,表达的是is a的关系,抽象类包含了子类通用特性,将子类存在差异化的特性进行抽象,交由子类去实现
    而接口是对行为的抽象,表达的是like a的关系,接口核心是定义行为,即实现类可以做什么具体怎么做,接口并不关心
    使用场景:当你关注一个事物的本质的时候,用抽象类,当你关注一个操作的时候,用接口
    抽象类的功能远超过于接口,但是接口的代价高,因为每个类只能继承一个父类,在这个类中你就需要写出子类的所有共性,但是却可以实现多个接口,在设计阶段降低了难度

6、List和Set的区别:

  • List: 有序,按对象进入的顺序保存对象,可重复,允许多个null 元素对象,可以使用Iterator取出所有元素,在逐一遍历,还可以根据下标索引的方式获取指定索引的元素
  • Set:无序,不可重复,最多只允许一个null元素,取元素的时候只能通过Iterator接口取得所有的元素,再逐一遍历各个元素

7、hashcode与equals

  • 如果两个对象相等,则hashCode也一定也是相同的
  • 两个对象相等,两个对象分别调用equals方法都返回true
  • 两个对象有相同的hashcode值,他们也不太一定的相等的
  • equals方法被覆盖,则hashcode方法也必须被覆盖
  • hashcode的默认行为是对堆上的对象产生的独特值,如果没有重写hashcode,则两个对象无论如何都不会相等(即使这两个对象指向相同的数据)

8、ArrayList和LinkedList区别

  • ArrayList: 基于动态数组,连续内存存储,适合下标访问,扩容机制,因为数组长度固定,超出长度存数据需要重新建数组,然后将老数组拷贝到新数组,如果不是尾部插入数据还会涉及到数据的移动,(往后复制一份,插入新元素),使用尾插法并指定初始容量可以极大的提高性能,甚至超过linkedlist(需要大量创建node节点)
  • 基于链表,可以存储在分散的内存中,适合做数据插入及删除操作,不适合做查询,需要逐一遍历linkedlist必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大,另外,不要尝试用indexof对其进行遍历,因为当没有这个数据的时候会将整个链表进行遍历,这样极其消耗性能

9、HashMap和HashTable的区别

区别:

  • HashMap方法没有synchronized修饰,线程非安全,HashTable线程安全
  • HashMap允许key 和value为null,而HashTable不允许
    底层实现:数组+链表
    jdk8开始链表高度到8,数组长度达到64,链表转变为红黑树,元素以内部类node节点存在
  • 计算key的hash值,二次hash然后对数组长度取模,对应到数组下标
  • 如果没有产生hash冲突,则直接将创建node存入数组
  • 如果产生hash冲突,先进行equals比较,相同则取代该元素,不同,则判断该链表高度插入链表,链表高度达到8,并且数组长度达到64则转变为红黑树,长度低于6则将红黑数转回链表
  • key为null,存储在下表为0的位置
    数组扩容,当存储数据达到负载因子的时候会重新创建一个数组将其进行拷贝过去

10、ConcurrentHashMap原理,jdk7和jdk8的区别

  • jdk7:
    数据结构:ReetrantLock + Segment + HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构
    元素查询:二次hash,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部
    锁:Segment分段锁 Segment继承了ReetrantLock,锁定操作的Segment,其他的Segment不受影响,并发度为Segment个数,可以通过构造函数指定,数组扩容不会影响其他的Segment
    get方法无需加锁,volatile保证
  • jdk8:数据结构synchronized(扩容,hash冲突)+CAS+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
    查找,替换,赋值操作都是用CAS
    锁:锁链表的head节点,不影响其他元素的读写,锁力度更细,效率更高,扩容时,阻塞所有的读写操作,并发扩容
    读操作无锁:
    Node的val和next使用volatile修饰,读写线程对该变量互相可见
    数组用volatile修饰,保证扩容时被读线程感知不去读脏数据

11、如何实现一个IOC容器

1、配置包扫描路径
2、递归包扫描获取.class文件
3、反射,确定需要交给IOC管理的类
4、对需要注入的类进行依赖注入

  • 配置文件扫描需要的包路径
  • 定义一些注解,分别表示访问控制层,业务逻辑层,数据持久层,依赖注入注解,获取配置文件注解
  • 从配置文件中获取需要扫描的包路径,获取到当前路径下的文件夹信息,我们将当前路径下所有以.class结尾的文件添加到一个Set集合中进行存储
  • 遍历这个Set集合,获取在类上有指定注解的类,并将其交给IOC容器,定义一个安全的Map用来存储这些对象
  • 遍历这个IOC容器,获取到每一个类的实例,判断里面是否有其他依赖,然后进行递归注入

12、什么是字节码,采用字节码的好处是什么

java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供了一个共同的接口。
编译器只需要面向虚拟机,生成虚拟机能够理解的代码叫做字节码(即扩展名为.class的文件),他不面向任何特定的处理器,只面向虚拟机
每一种平台的解释器都是不同的,但是实现的虚拟机是相同的,java源程序编译后编程字节码,字节码由虚拟机解释执行,虚拟机将每一条需要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后再特定的机器上运行,这也就是解释了java的编译与解释并存的特点
javay源代码-->编译器-->jvm可执行的java字节码(即虚拟指令)-->jvm-->jvm中解释器-->机器可执行的二进制机器码-->程序执行
采用字节码的好处
java语言通过字节码的方式,在一定程度上解决了传统解释型语言可移植的特点。所以java程序运行时比较高效,而且,由于字节码并不专对一种特点的机器,因此,java程序无需重新编译便可在多种不同的机器上运行

13、java类加载器有哪些

jdk自带有三各类加载器:bootstrapClassloader,ExtClassLoader,AppClassLoader
bootstrapClassloader是ExtClassLoader的父类加载器,默认负责加载%JAVA_HOME%/lib下的jar包和class文件
ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME%/lib/ext文件夹下的jar包和class类
AppClassLoader是自定义类加载器的父类,负责加载classpath下的类文件
继承classloader实现自定义的类加载器

14、双亲委派机制

向上查找缓存,直到bootstrapclassloader,这里没有缓存就会到相应的加载路径去加载,这里有相应的类就会加载,没有相应的类就会向下查找(向上查找到顶层,向下查找到自定义类加载器)
双亲委派的好处:

  • 主要是为了其安全性,避免用户自己编写的类动态的替换java的一些和心类,比如String
  • 同时也避免了类的重复加载,因为JVM中区分不同类,不仅仅是根据类名,相同的class文件被不同的ClassLoader加载器加载的就是两个类

15、java中的异常体系

java中的所有的异常都来自顶级父类Throwable
Throwable下有两个子类Exception和Error
Error是程序无法处理的异常,一旦出现这个错误,则程序将被迫停止运行
Exception不会导致程序停止,又分为两部分RunTimeException运行时异常和CheckedException检查异常
RuntimeException常常发生在程序运行过程中,会导致程序当前线程执行失败,CheckException常常发生在程序编译过程中,会导致程序编译不通过

16、GC如何判断对象可以被回收

  • 引用计数法:每个对象有一个引用技数属性,新增一个引用计数加一,引用释放时计数减一,计数为0时可以回收(有一个缺点,两个对象相互引用时这两个对象就无法回收)
  • 可达性分析:从GC Roots开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象

GC Roots的对象有哪些:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

可达性分析中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会,对象被系统宣告死亡至少经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在虚拟机自动建立的Finalize队列中判断是否需要执行finalize()方法。
当对象编程(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收,否则,若对象未执行finalize方法,将其放入F-Queue队列,由一低优先级执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”
每个对象只能触发一次finalize方法
由于finalize方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,减一遗忘他

17、现成的生命周期?线程有几种状态

1.线程通常有5种状态,创建,就绪,运行,阻塞和死亡状态
2.阻塞状态又分为三种:
(1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中,进入这个状态后,是不能自动唤醒的,必须依赖其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
(2)、同步阻塞:运行的线程在获取同步锁时,若该同步锁被别的线程占用,则jvm会把该线程放入“锁池”中
(3)、其他阻塞:运行的线程执行sleep或join方法,或则发出了I/O请求时,JVM会把该线程设置为阻塞状态。当sleep状态超时,join等待线程终止或则超时,或则I/O处理完毕时,线程重新进入就绪状态,sleep是Thread类的方法

  • 新建状态(New):新创建了线程对象
  • 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法,改状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权
  • 运行状态(Running):就绪状态的线程获取了CPU,执行程序代码
  • 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU的使用权,暂时停止运行,线程进入就绪状态,获取相应的资源后转到运行状态
  • 死亡状态(Dead):线程执行完了或则因为异常退出了run方法,该线程结束生命周期

18、sleep(),wait(),join(),yield()的区别

1.锁池:所有线程需要竞争同步锁的线程都会放在锁池中,比如当前线程对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后就会进入就绪队列进行等待cpu资源的分配
2.等待池:当我们调用wait方法后,线程会释放到等待池当中,等待池的线程是不会去竞争同步锁,只有调用了notify或notifyAll后等待线程才会开始去竞争锁,notify是随机从等待池中选出一个线程放到锁池,而notifyAll是将等待池的所有线程放到锁池当中

1.sleep是Thread类的静态方法,wait则是Object类的本地方法
2.sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中

sleep就是把cpu的执行资格和执行权释放出去,不再运行此线程,当定时时间结束再取回cpu资源,参与cpu的调度,获取到cpu资源后就可以继续运行了,而如果sleep时该线程有锁,那么sleep不会释放这个锁,而是把锁带着进入冻结状态,也就是说其他需要这个锁的线程根本不可能获取到这个锁,,也就是说无法执行程序,如果再睡眠期间其他线程调用了这个线程的interrupt方法,那么这个线程也会抛出interruptexception异常返回,这点和wait是一样的

3.sleep方法不依赖于同步器synchronized,但是wait需要依赖synchronized关键字
4.sleep不需要被唤醒,但是wait需要(不指定时间需要被其他线程唤醒)
5.sleep一般用于当前线程休眠,或则轮询暂停操作,wait则多用于多线程之间的通信
6.sleep会让出cpu执行时间强制上下文交换,而wait则不一定,wait后可能还是有机会重新竞争到锁继续执行的

yield执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行
join执行后线程进入阻塞状态,例如在线程B中调用线程A的join,那么线程B会进入到阻塞队列,直到线程A结束或中断线程

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(3000);
                    System.out.println("22222222");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t1.join();
        //这行代码必须要等t1这个线程执行完毕才会执行
        System.out.println("11111111");
    }

19、谈谈你对线程安全的理解

不是线程安全,应该是内存安全,堆是共享内存,可以被所有线程访问

当多个线程访问一个对象时,如果不用进行额外的同步控制或其他的协调操作,调用这个对象的行为都可以获得正确的结果,我们就说这个对象是线程安全的

堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不就会造成内存泄漏

在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存

栈是每个线程独有的,保存其u运行状态和局部自动变量的,栈在线程开始的时候初始化,每个线程的栈相互独立,因此,栈是线程安全的,操作系统在切换线程的时候会自动切换栈,栈空间不需要在高级语言里面显示的分配和释放

目前主流操作系统都是多任务的,即多个进程同时运行,为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的
在每个进程的空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域,这就是造成问题的潜在原因

20、Thread,Runnable的区别

Thread和Runnable的实质继承关系,没有可比性,无论使用Runnable还是Thread,都会new Thread,然后执行run方法,用法上,如果有复杂的线程操作需求,那么就选择继承Thread,如果只是简单的执行一个任务,那么就是先runnabe。

21、说说你对守护线程的理解

守护线程:为所有的线程提供服务的线程,任何一个线程都是真个jvm中所有非守护线程的保姆
守护线程类似于整个进程的一个默默无闻的小喽喽,他的生死无关紧要,他却以来整个进程而运行,等待其他进程都结束了,没有需要执行的了,程序就结束了,理都不理守护线程就把他中断了
注意:由于守护线程的终止是自身无法控制的,因此千万不要把IO,File等重要的操作逻辑分配给他,因为他不靠谱

守护线程的作用是什么:
举例,GC垃圾回收线程:就是一个经典的守护线程,当我们的程序不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收期器也就是无事可做,所以当垃圾回收线程是jvm上仅剩的线程时,垃圾回收线程会自动离开,他始终在低级别的状态中运行,用于实时监控和管理系统中可回收资源
应用场景:(1)来为其他线程提供服务支持的情况;(2)或则在任何情况下不再有任何运行的Thread,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用,反之,如果一个正在执行某个操作的线程必须要正确地关闭否则就会出现不好的后果的话,那么这个线程就不能设置未守护线程,而是用户线程,通常都是写关键的事物,比方说,数据库录入或则更新,这些操作都是不能中断的

thread.setDaemon(true)必须是线程启动之前,否则会抛出一个illegalThreadStateException异常,你不能把正在运行的常规线程设置为守护线程

在Daemon线程中产生的新线程也是守护线程

守护线程不能用于去访问固有资源,比如读写操作或则计算逻辑,因为他会在任何时候甚至在一个操作的中间发生中断

Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果使用后台线程就不能用java的线程池

22、ThreadLocal的原理和使用场景

每一个Thread对象均包含有一个ThreadLocalMap类型的成员变量threadLocals,他存储本线程中所有ThreadLocal对象及其对应的值

ThreadLocalMap由一个个Entry对象构成

Entry继承自WeakReference>,一个Entry由ThreadLocal对象和Object对象构成,由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用,当没指向key的强引用后,该key就会被垃圾收集器回收
当执行set方法,ThreadLocal首先会获取当前对象线程对象,然后获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中

get方法执行过程类似,ThreadLocal首先会获取当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,获取对应的value
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性

使用场景:

  • 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束
  • 线程间数据隔离
  • 进行实物操作,用于存储线程事务信息
  • 数据库连接,Session会话管理

Spring框架在事务开始时会给当前线程绑定一个Jdbc Connection,在整个事务过程都是使用线程绑定的connection来执行数据库操作,实现了事物的隔离性,Spring框架里面就是用的ThreadLocal来实现这种隔离

23、ThreadLocal内存泄漏原因,如何避免

内存泄漏为程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏危害可以忽略,但内存泄漏堆积后果很严重,无论多少,迟早会被占光
不再会被使用的或则变量占用的内存不能被回收,就是内存泄漏

强引用:使用最普通的引用(new),一个对象具有强引用,不会被垃圾回收器回收,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会受这种对象
如果想取消强引用和某个对象之间的关联,可以显示地赋值为null,这样可以将jvm在合适的时间就会回收该对象
弱引用:jvm进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象,在Java中,用java.lang.ref.WeakReference类来表示,可以在缓存中使用弱引用

ThreadLocal的实现原理,每一个Thread维护一个ThreadLocalMap,key为使用弱引用的ThreadLocal实例,value为线程变量的副本

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,key(ThreadLocal:强引用不存在了,当GC进行垃圾回收的时候就会回收)势必会被GC回收,这样就会导致ThreadLocalMap中key为null,而value还存在着强引用,只有thread线程推出以后value的强引用链条才会断掉,但如果当前线程迟迟不结束的话,这些key为null的Entry的value就会一致存在一条强引用链
key使用强引用:
当threadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key使用弱引用:
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收,当key为null,在下一次ThreadLocalMap调用set,get,remove方法的时候会被清除value值

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用

ThreadLocal正确的使用方法

  • 每次使用完ThreadLocal都调用他的remove方法清除数据
  • 将ThreadLocal变量定义成private static,这样就一致存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉


    image.png

24、兵并发,并行,串行的区别

  • 穿行在时间上不可能发生重叠,前一个任务没有高度,下一个任务就只能等着
  • 并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行
  • 并发允许两个任务彼此干扰,同一时间点,只有一个任务运行,交替执行

25、并发的三大特性

  • 原子性
    原子性是指在一个操作中cpu不可以中途停止然后再调度,即不被该中断操作,要不全部执行,要不都不执行。就好比转账,从账户A向账户B转1000源,那么必然包括2个操作,从账户A减去1000元,往账户B加上1000元,2个操作必须全部完成
private long count = 0
public void calc(){
  count++;
}
  • 1:将count从贮存读到工作内存中的副本中
  • 2:+1的运算
  • 3:将结果写入到内存
  • 4:将工作内存的值刷回主存(什么时候刷回由操作系统决定,不确定的)

那程序中原子性指的是最小的操作单元,比如自增操作,它本身其实并不是原子性操作,分了3步的,包括读取变量的原始值,进行加1操作,写入工作内存,所以在多线程中,有可能一个线程还没自增完,可能才执行到第二步,另一个线程已经读取了值,导致结果错误。那如果我们保证自增操作是一个原子性的操作,那么就能保证其他线程读取到的一定是自增后的数据
关键字:synchronized
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即查看得到修改的值
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程2没看到这就是可见性问题

//线程1
boolean stop = false;
while(stop){
  doSomething();
}
//线程2
stop = true;

如果下次2改变了stop的值,线程1一定会停止吗?不一定,当线程2更改了stop变量的值之后,多少还没来得及写入主存当中,线程2转去2去做其他事情了,那么线程1由于不知道下次2对stop变量的更改,因此还会一致循环下去
关键字:volatile,synchronized,final

  • 有序性
    虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序执行,有可能将他们中心排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题
int a = 0;
boolean flag = false;
public void write(){
  a = 2;             // 1   
  flag = true;       //2
}
public void multiply(){
  if(flag) {         //3
    int ret = a*a;   //4
  }
}

write方法里的1和2做了重排序,线程1先对flag赋了值为true,随后执行线程2,ret直接算出结果,再到线程1,这时候a才赋值为2,恨明显迟了一步
关键字:volatile,synchronized
volatile本身包含了禁止指令重拍的语义,而synchronized关键字是由一个变量在同一时只允许一条线程对其进行操作

synchronized关键字同时满足以上三种特性,但是volatile关键字不满足原子性
在某些情况下,volatile的同步机制的性能确实要优于锁(使用synchronized关键字或java.util.concurrent包里面的锁),因为volatile的总开销要比锁低

26、为什么使用线程池?解释下线程池参数?

1.降低资源消耗,提高线程利用率,降低创建和销毁线程的消耗
2.提高效应速度,任务来了,直接有线程可用可执行,而本身先创建线程,再执行
3.提高线程的可管理性,线程是稀缺资源,使用线程池可以统一分配调优监控

  • corePoolSize 代表核心线程数,也就是正常情况下创建工作的线程数,这些线程创建后并不会消除,而是一种常驻线程
  • maxinumPoolSize 代表的是最大线程数量,他与核心线程数相对应,表示最大允许创建的线程数,比如当前线程任务较多,队列任务中已满并且当前已经创建的线程数小于maxinumPoolSize,那么此时将会创建新的线程,但是线程池内线程总数不会超过最大线程数(setMaximumPoolSize可以对最大线程数进行修改)
  • keepAliveTime, unit 表示超出核心线程数之外的线程的空闲存活时间,也就是核心线程不会消除,但是超出核心线程数的部分如果空闲时间超出制定的存活时间就会被消除,我们可以通过setKeepAliveTime来设置空闲时间
  • workQueue 用来存放待执行的任务,假设我们现在核心线程都已经被使用,还有任务进来则全部放入对列,直到整个队列被放满但任务还在持续进入则会开始创建新的线程
  • ThreadFactory 实际上是一个线程工程,用来生产线程执行任务,我们可以选择使用默认的创建线程,产生的线程都在同一个组内,拥有相同的优先级,且都不是守护线程,当然我们也可以选择自定义线程工厂,一般我们会根据业务来制定不同的线程工厂
  • Handler 任务拒绝策略,有两种情况 ,第一种是当我们调用shutdown等方法关闭线程池后,这时候即使线程池内部还有没执行完的任务正在执行,但是由于线程池已经关闭,我们再继续想线程池提交任务就会遭到拒绝,另一种情况就是达到最大线程数,,队列也装满了,这时候没有能力处理线程或则排队的空间就会拒绝

27、简述线程池的处理流程

image.png

28、线程池中阻塞队列的作用?为什么是先添加队列而不是先创建最大线程

1.一般的队列只能保证作为一个有线长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留当前想要继续入队的任务
阻塞队列可以保证任务队列中没有任务时,阻塞获取任务的线程,使得线程进入wait状态,释放cpu资源
阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一致占用cpu资源

2.在创建新线程的时候,是要获取全局锁的,这个时候其他的就得阻塞,影响了整体效率

29、线程池中线程复用原理

线程池将线程和任务进行解耦,线程是线程,任务是任务,拜托了之前通过Thread创建线程时的一个线程必须对应一个任务的限制
在线程池中,同一个任务可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread进行了封装,并不是每次执行任务都会调用Thread.start来创建新线程,而是让每个线程去执行一个循环任务,在这个循环任务中不停检查是否有任务需要被执行,如果有直接执行,也就是调用任务中的run方法,将run方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的run方法串联起来

30、spring是什么

轻量级的开源的J2EE框架,他是一个容器框架,用来装javabean(Java对象),中间层框架(万能加),可以起一个连接作用,比如说把Struts和hibernate粘和到一起运用,可以让我们的企业开发更快,更简洁
spring是一个轻量级的控制反转和面向切面的容器框架

  • 从大小与开销两方面而言spring都是轻量级的
  • 通过控制反转的计数达到松耦合的目的
  • 提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发
  • 包含并管理应用对象的配置和生命周期,这个意义上是一个容器
  • 将简单的组件配置,组合成为复杂的应用,这个意义上是一个框架

31、谈谈你对AOP的理解

系统是由许多不同的组建所组成的,每一个组件各负责一块特定的功能,除了实现自身核心功能之外,这些组件还经常承担着额外的职责,例如日志,事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为他们会跨越系统的多个组件

当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力,也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系,例如日志功能

日志代码往往水平的散布在所有对象层次中,而与他所散布到的对象的核心功能毫无关系

在OOP设计中,他导致了大量代码的重复,而不利于各个模块的复用

AOP:将所有程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去,AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法之后额外的做一些事情

32、谈谈你对IOC的理解

容器概念,控制反转,依赖注入
IOC容器:实际上就是个map(key,value),里面存的是各种对象(在xml里配置的bean节点,@repository,@service,@component,@controller),在项目启动的时候会读取配置文件里的bean节点,根据全限定类名使用反射创建对象放到map里,扫描到上述注解的类还是通过反射创建对象放到map里。
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(autowired,resource等注解xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性更具id注入,也会扫描这些注解,根据类型或id注入,id就是对对象名)

控制反转:
没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或则运行到某一点的时候,自己必须主动去创建对象B或则使用已经创建的对象B,无论是创建还是使用对象B,控制权都在自己手上
引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方
通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变味了被动行为,控制权颠倒过来了,这就是控制反转这个名称的由来。
全部兑现的控制权全部上交给IOC容器,所以,IOC容器成了整个系统的关键核心,他起到了一种类似粘合剂的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个粘合剂,对象与对象之间会彼此失去联系,这就是悠人把IOC容器比喻成粘合剂的由来

依赖注入:
获得以来对象的过程被反转了。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器自动注入,依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态的将某种依赖关系注入到对象之中

33、BeanFactory和ApplicationContext有什么区别?

ApplicationContext是BeanFactory的子接口
ApplicationContext提供了更完整的功能:
1.继承MessageSource,因此支持国际化
2.统一的文件访问方式
3.提供在监听器中注册BEAN的时间
4.同事加载多个配置文件
5.载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层

  • BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean),c才对该对象进行加载实例化,这样,我们就不能发现一些存在的spring的配置问题,如果bean的某一个属性没有注入,BeanFactory加载后,直至第一次调用getBean方法才会抛出异常
  • ApplicationContext他是在容器启动的时候,一次性创建了所有的Bean,这样,在容器启动时,我们就可以发现spring中存在的配置错误,这样有利于检查所有依赖属性是否注入,ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean,确保当你需要的时候,你就不用等待,因为他们已经创建好了
  • 相对于基本的BeanFactory,ApplicationContext唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢
  • BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建如使用

34、描述一下spring bean

1.解析类得到BeanDefinition
2.如果有多个构造方法,则要推断构造方法
3.确定好构造方法后,进行实例化得到一个对象
4.对对象中加了@Autoware等注解的属性镜像注入
5.回调Aware方法,比如BeanNameAware,BeanFactoryAware(bean需要定义自己需要感知spring容器里的一些属性(需要感知那些就定义那些),bean需要实现上面这些接口并实现接口方法,然后将传入的参数设置到前面定义的属性里面,具体网上查看文章:https://blog.csdn.net/qq_38526573/article/details/88095674)
6.调用BeanPostProcessor的初始化前的方法(可以修改我们的bean属性,需要自定义实现这个接口)
7.调用初始化方法
8.调用BeanPostProcessor的初始化后的方法,在这里会进行AOP
9.如果当前创建的bean是单列的则会把bean放入单列池
10.使用bean
11.spring容器关闭调用DisposableBean中destory方法

35、说一下spring 支持的几种bean的作用域

  • singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护,该对象的声明周期与spring ioc容器一致(但在第一次被注入时才会被创建)
  • prototype:为每一个bean请求提供一个实例,在每次呼入时都会创建一个新的对象
  • request:bean被定义为在每个HTTP请求中创建一个单例对象,,也就是说在单个请求中都会复用这一个单例对象
  • session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之销毁
  • application:bean被定义为在ServletContext的声明周期中复用一个单例对象
  • websocket:bean被定义为在websocket的声明周期中复用一个单例对象

global-session:全局作用域,global-session和portlet应用相关,当你的应用部署在portlet容器中工作时,它包含很多portlet,如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中,全局作用域与Servlet中的session作用域效果相同

36、spring框架中的单例bean是线程安全的么?

spring中的bean默认是单例模式的,框架并没有对bean进行多线程的封装处理
如果bean是有状态的,那么就需要开发人员自己来进行安全的保证,最简单的办法就是改变bean的作用域把singleton改为prototype这样每次请求bean就相当于是新创建了一个bean,这样就可以保证线程安全了

  • 有状态就是数据存储功能
  • 无状态就是不会存储数据,controoler,service和dao层本身并不是线程安全的,如果只是调用里面的方法,而且多线程调用一个单例的方法,会在全局中复制变量,这是自己的线程的构造内存,是安全的
    dao会操作数据库connection,connection是带有状态的,比如说数据库事务,spring的事务管理器使用ThreadLocal为不同的线程维护了一套独立的connection副本,保证线程之间不会互相影响(spring是如何保证事务获取同一个connection的)
    不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronized,lock,CAS等这些实现线程同步的功能了

37、spring框架中都用到了那些设计模式

简单工厂:由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类

spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获取Bean对象,但是否是在传入参数后创建还是传入参数前创建这个要根据具体情况来定

工厂方法:

实现了FactoryBean接口的bean是一类叫做factory的bean,其特点是,spring会在使用getBean调用获得该bean时,会自动调用该bean的getObject方法,所以返回的不是factory这个bean,而是bean.getObject方法的返回值

单例模式:保证一个类仅有一个实例,并提供一个访问他的全局访问点

spring对单例的实现:spring中的单例模式完成了后半句话,即提供了全局访问点BeanFactory,但没有从构造器级别去控制单例,这是因为spring管理的是任意的Java对象

适配器模式:

spring定义了一个适配接口,使得每一种controller有一种对应的适配器实现类,让适配器代替controller执行相应的方法,这样就扩展controoler时,只需要增加一个适配器类就完成了SpringMVC的扩展了

装饰器模式:动态的给一个对象添加一些额外的职责,就增加功能来说,Decorator模式相比生成子类更为灵活

spring中用到的包装器模式在雷鸣上有两种表现,一种是类名中含有Wrapper,另一种是类中含有Decorator

动态代理:

切面在应用运行时的时刻被织入,一般情况下,在植入切面时,AOP容器会为缪奥对象动态创建一个代理对象,springaop激素以这样方式织入切面的
织入:把切面应用到目标对象并创建新的代理对象的过程

观察者模式:

spring的事件驱动模型使用的是观察者模式,spring中observer模式常用的地方是listener的实现

策略模式:

spring框架的资源访问Resource接口,该接口提供了更强的资源访问能力,spring框架本身大量使用了resource接口来访问底层实现

装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模 式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器。

38、spring事务的实现方式和原理以及隔离级别

在使用spring框架时,可以有两种使用事务的方式,一种是编程式,一种是声明式的,@Transcation注解就是声明式的
首先,事务这个概念是数据库层面的,spring只是基于数据库中的事务进行了扩展,以及提供了一些让程序员更加方便操作事物的方式
比如我们可以通过在某个方法上添加@Transcation注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功或失败
在一个方法上加了@Transcation注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当使用这个代理对象时,如果这个方法上存在@Transcation注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚
当然,针对那些异常回滚事务是可以配置的,可以利用@Transcation注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚

spring事务隔离级别就是数据库的隔离级别:外加一个默认级别

  • read uncommitted(未提交读):读写进行,解决了更新丢失,可能会出现脏读
  • read committed(提交读,不可重复读):读写进行,写事务会限制其他事务读取该行数据,A读,B写,A再读数据已经发送了变化,解决了更新丢失和脏读问题
  • repeatable read(可重复读):写会禁止读事务,读会禁止读事务不会限制读事务,解决了更新丢失、脏读、不可重复读、但是还会出现幻读
  • serializable(可串行化):事务队列化

数据库的配置隔离级别是read commited,而spring配置的隔离级别是repeatable read,请问这时隔离级别是以那一个为准?以spring为准,如果spring配置的隔离级别数据库不支持,效果取决于数据库

39、事务的传播机制

spring事务传播机制
多个事务方法相互调用时,事务如何在这些方法间传播

方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同队徽对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的类型所决定的

REQUIRED(spring默认的事务传播类型):如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
SUPPORTS:当前存在事务,则加入当前十五,如果当前没有事务,就以非事务方法进行
MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常
REQUIRES_NEW:创建一个新事物,如果存在当前事务,则挂起该事务
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务
NEVER:不使用事务,如果当前事务存在,则抛出异常
NESTED:如果当前事务存在,则在潜逃事务中执行,否则REQUIRED的操作一样(开启一个事物)

和REQUIRES_NEW的区别
REQUIRES_NEW是新创建一个事务并且新开启的这个事务与原事务无关,而NESTED则是当前存在事务时(我们把当前事务称之为父事务)会开启一个嵌套事务(称之为一个子事务),在NETSTED情况下父事务回滚时,子事务也会回滚,而在REQUIRES_NEW情况下,原有事务回滚,不会影响新开启的事务
和REQUIRED的区别
REQUIRED情况下,调用方存在事务时,则被调用方和调用使用同一事务,那么被调用放出现异常时,由于供用一个事务,所以无论调用方是否catch其异常,事务都会回滚,而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不受影响

40、spring事务什么时候会失效

spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种

  • 1.发生自调用,类里面使用this调用本类的方法(this通常省略),此时这个this对象不是代理类,而是UserService对象本身
    解决方法很简单,让那个this变成UserService的代理类即可
  • 2.方法不是public的

@Transcational只能用于public的方法上,否则事务会失效,如果要用在非public方发上,可以开启Aspect代理模式

  • 3.数据库不支持事务
  • 4.没有被spring管理
  • 5.异常被吃掉,事务不会回滚(或则抛出异常没有被定义,默认为RuntimeException)

41、什么是bean的自动装配,有哪些方式

开启自动装配,只需要在xml配置文件中定义autowire属性

autowire属性有五种装配的方式:

  • no -缺省情况下,自动装配是通过ref属性手动设定

手动装配:以value或ref的方式明确指定属性值都是手动装配

  • byName - 根据bean的属性名称进行自动装配

Customer的属性名称person,Spring会将bean id为person的bean通过setter方法进行自动装配

  • byType - 根据bean的类型进行自动装配

Customer的属性person的类型为Person,Spring会将Person 类型的bean通过setter方法进行自动装配

  • constructor - 类似byType,不过是应用于构造器的参数,如果一个bean与构造器参数的类型形同,则进行自动装配,否则导致异常

Customer构造函数的参数person的类型为Person,spring会将Person类型通过构造方法进行自动装配

  • autodetect - 如果有默认的构造器,则通过contructor方式进行自动装配,否则使用byType方式进行自动装配

如果有默认的构造器,则通过constructor方式进行自动装配,否则使用byType方式进行自动装配

42、spring boot,spring mvc和spring

spring是一个IOC容器,用来管理bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制弥补OOP的代码重复问题,更方便将不同方法中的共同处理抽取成切面,自动注入给方法执行,比如日志,异常等

springmvc是spring提供的一个快速开发web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接受请求,然后定义了一套路由策略(url到handle的银蛇)即适配执行handle,将handle结果使用试图解析计数生成视图展现给前端

springboot是spring提供的一个快速开发工具包,让程序员能更方便,更快速的开发spring+springmvc应用,简化了配置(约定了默认配置),整合了一系列的解决方案(starter机制),redis,mongodb,es,可以开箱即用

43、SpringMVC工作流程

1.用户发送请求到前端控制器DispatcherServlet
2.DispatcherServlet收到请求调用HandlerMapping处理映射器
3.处理器映射器找到具体的处理器(可以根据xml配置,注解进行查找),生成处理器及拦截器(如果有则生成)一并返回给DispatcherServlet
4.DispatcherServlet调用HandlerAdapter处理器适配器
5.HandlerAdapter经过适配器调用具体的处理器(Controller,也叫后端控制器)
6.Controller执行完成返回ModelAndView
7.HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器
9.ViewResolver解析后返回具体View
10.DispatcherServlet根据View进行渲染视图(将模型数据填充到视图中)
11.DispatcherServlet响应用户

44、SpringMVC的主要组件

Handler:也就是处理器,他直接对应着MVC的c也就是Controller层,他的具体表现形式有很多,可以实类,也可以是方法,在Controller层中@RequestMapping标注的所有方法都可以看成是一个Handler,只要可以实际处理请求就可以是Handler
1.HandlerMapping
iniHandlerMapping(context),处理器映射器,根据用户请求资源的url来查找Handler的,在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用那个Handler进行,这就是HandlerMapping需要做的事

2.HandlerAdapter
initHandlerAdapter(context),适配器,因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就OK,但是Servlet需要的处理方法的结构确实固定的,都是以request和response为参数的方法,如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是Handler要做的事情
Handler是哦那个来干活的工具,HandlerMapping用来根据需要干的活找到相应的工具,HandlerAdapter是使用工具干活的人

3.HandlerExceptionResolver
initHandlerExceptionResolvers(context),其他组件都是用来干活的,在干活的过程中难免会出问题,出问题后怎么办呢?这就需要有一个转门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver,具体来说,此组建的作用是根据异常设置ModelAndView,之后再交给render方法进行渲染

4.ViewResolver
initViewResolvers(context),ViewResolver用来将String类型的视图和Local解析为View类型的视图,View是用来渲染页面的,也就是将程序返回的参数植入模板里,生成html(也可能是其他类型)文件。这里就有两个关键问题,使用那个模板?用什么计数(规则)之恶u参数?这其实是ViewResolver主要做的工作,ViewResolver需要找到渲染所用的模板和所用的计数(也就是使徒的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成

5.RequestToViewTranslator
initRequestToViewNameTranslator(context),ViewReslver是根据ViewName查找View,但有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了,如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了,RequestToViewNameTranslator在SpringMVC容器里只可以配置一个,所以所有request到ViewName的转换规则都要在一个Translator里面全部实现

6.LocalResolver
initLocalResolver(context),解析视图需要两个参数,一是视图名,另一个是Local,视图名是处理器返回的Local是从哪里来的?这就是LocalResolver要做的事情,LocalResolver用于从request解析出Local,Local就是zh-cn之类,标识一个区域,有了这个就可以对不同区域的用户显示不同的结果,SpringMVC主要有两个地方用到了Local:一是ViewResolver视图解析的时候;二是用到国际化资源或则主题的时候

7.ThemeResolver
initThemeResolver(context),用来解析主题,SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源,如图片,css样式等,SpringMVC的主题也支持国际化,同一个主题不同区域也可以显示不同的风格,SpringMVC中跟主题相关的类有ThemeResolver,ThemeResource和Theme,主题是通过一系列资源来具体实现的,要得到一个主题的资源,首先要得到资源的名称,这是ThemeResolver的工作,然后通过主题名称找到对应的主题(可以理解为一个配置)文件,这是ThemeSource的工作,最后主题中获取资源就可以了

8.MultipartResolver
initMultipartResolver(context),用来处理上传请求,处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File,如果上传多个文件,还可以调用getFileMap得到FileName -> File结构的Map,此组件中一共有三个方法,作用分别是判单是不是上传请求,将request包装成MultipartHttpServletRequest,处理完后清理上传过程中产生的临时资源

9.FlashMapManager
initFlashMapManager(context),用来管理FlashMap的,FlashMap主要用在redirect中传递参数

45、SpringBoot自动配置原理

@Import + @Configuration + Spring spi
自动配置类由各个starter提供,使用@Configuration + @Bean定义配置类,放到META-INF/spring.factories下使用Spring spi扫描META-INF/spring.factories下的配置类
使用@Import导入自动配置类
(https://blog.csdn.net/u014745069/article/details/83820511)

46、如何理解Spring Boot中的Starter

使用spring + springmvc使用,如果需要引入mybatis等框架,需要到xml中定义mybatis需要的bean starter就是定义一个starter的jar包,写一个@Configuration配置类,将这些bean定义在里面,然后在starter包的META-INF/spring.factories中写入改配置类
开发人员只需要将相应的starter包依赖引入,进行相应的属性配置(使用默认配置时,不需要配置),就可以直接进行代码开发,使用对应的功能了,比如mybatis-spring-boot-starter,spring-boot-starter-redis

47、什么是嵌入式服务器,为什么要使用嵌入式服务器

节省了下载安装tomcat,应用也不需要再打war包,然后放到webapp目录下再运行
只需要一个安装了java的虚拟机,就可以直接在上面部署应用程序了
springboot已经内置了tomcat.jar,运行main方法时会去启动tomcat,并利用tomcat的spi机制加载springmvc

48、mybatis的优缺点

优点:
1.基于SQL语句编程,相当灵活,不会对应用程序或则数据库的现有设计造成任何影响,SQL写在XML里,接触sql与程序代码的耦合,便于统一管理,提供XML标签,支持编写动态SQL语句,并可重用
2.与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
3.很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)
4.能够与Spring很好的集成
5.提供映射标签,支持对象与数据库的ORM字段的关系映射,提供对象关系映射标签,支持对象关系组件维护

缺点:
1.SQL语句的编写工作量较大,尤其当字段多,关联表多时,对开发人员编写SQL语句的工地有一定要求
2.SQL语句依赖于数据库,导致数据库移植性较差

49、MyBatis与Hibernate对比

SQL和ORM的争论,永远都不会停止
开发速度的对比:
Hibernate的真正掌握要比mybatis难,mybatis框架相对简单容易上手,但也相对简陋些
比起两者的开发速度,不仅仅要考虑到两者的特性及性能,更要根据项目需求去考虑究竟哪一个更合适项目开发,比如:一个项目中用到的复杂查询基本没有,就是简单的增删改查,这样选择hibernate效率就快很多了,因为基本的sql语句都已经封装好了,根本需要你去写sql语句,这就节省了大量的时间,但是对于一个大型项目,复杂语句较多,这样再去选择hibernate就不是一个很好的选择,选择mybatis就会加快很多,而且语句的管理也比较方便
开发工作量的对比
Hibernate与Mybatis都有相应的代码生成工具,可以生成简单基本的DAO层方法,针对高级查询,mybatis需要手动编写SQL语句,以及ResultMap,而Hibernate有良好的映射机制,开发者无需关心SQL的生成与结果映射,可以更专心专注于业务流程

sql优化方面:
Hibernate的查询会将表总的所有字段查询出来,这一点会有性能消耗。Hibernate也可以自己写SQL来制定需要查询的字段,但这样就破坏了Hibernate开发的简洁性,而Mybatis的SQL是手动编写的,所以可以按需求指定查询字段
Hibernate HQL语句的调优需要将sql打印出来,而Hibernate的SQL被很多人嫌弃因为太丑了,mybatis的SQL是自己手动编写的所以调整方便,但Hibernate具有自己的日志统计,mybatis本身不带日志统计,使用log4j进行日志记录

对象管理的对比:
Hibernate是完整的对象/关系映射机制的解决方案,他提供了对象状态管理(state management)的功能,使开发者不再需要理会底层数据库系统的细节,u也就是说,相对于常见的JDBC/SQL持久层方案中需要管理SQL语句
Hibernate采用了更自然的面向对象的视角来持久化java应用中的数据
换句话说,使用Hibernate的开发者应该总数关注对象的状态,不必考虑SQL语句的执行,这部分细节已经由Hibernate掌管妥当,只有开发者再进行系统性能条有的时候才需要进行了解,而mybatis在这一块没有文档说明,用户需要对对象自己进行详细的管理

缓存机制对比
相同点:都可以实现自己的缓存或使用其他第三方缓存方案,创建适配器来完全覆盖缓存行为
不同点:Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是那种缓存
mybatis的耳机缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制,并且mybatis可以在命名空间中共享相同的缓存配置和实例,通过cache-ref来实现
两者比较:因为Hibernate对查询对象有着良好的管理机制,用户无需关心SQL,所以在使用二级缓存时如果出现脏数据,系统会爆出错误提示
而mybatis在这一方面,使用二级缓存需要特别小心,如果不能完全确定数据更行操作的波及范围,避免cache的盲目使用,否则,脏数据的出现会给系统的正常运行带来很大的隐患

Hibernate功能强大,数据库无关性好,O/R映射能力强,如果你对Hibernate相当精通,而且对Hibernate进行了适当的封装,那么你的项目整个持久层代码会相当简单,需要写的代码很少,开发速度很快,非常爽。

Hibernate的缺点就是学习门槛不低,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡取得平衡,以及怎样用好Hibernate方面需要你的经验和能力都很强才行。

iBATIS入门简单,即学即用,提供了数据库查询的自动对象绑定功能,而且延续了很好的SQL使用经验,对于没有那么高的对象横型要求的项目来说,相当完美。

iBATIS的缺点就是框架还是比较简陋,功能尚有缺失,虽然简化了数据绑定代码,但是整个底层数据库查询实际还是要自己写的,工作量也比较大,而且不太容易适应快速数据库修改,

50、#{}和${}的区别是什么

1.#{}是预编译处理、是占位符。S{}是字符串替换、是拼接符

2.Mybatis 在处理#{}时,会将sql 中的#{}替换为?号,调用PreparedStatement来赋值

3.Mybatis在处理${}时,就是把s{}替换成变量的值,调用Statement来赋道

4.#{}的变量替换是在DBMS中、变量替换后,1督对应的变量白动加上单引号

5.${}的变量替换是在DBMS外、变量替换后,S{}对应的变量不会加上单引号

6.使用#{}可以有效的方止 SQL注入,提亮系统安全性。

51、简述mybatis的插件运行原理,如何编写一个插件

答: Mybatis.只支持针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis 使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的 invoke()方法,拦截那些你指定需要拦截的方法。
(插件编写实例:https://blog.csdn.net/weixin_44046437/article/details/100526643)

52、索引的基本原理

索引用来快速的寻找那些具有特定值的记录,如果没有索引,一般来说执行查询时遍历整张表
索引的原理:就是把无序数据变成有序的查询
1.把创建了索引的列的内容进行排列
2.堆排序结果生成倒排表
3.在倒排表内容上拼上数据地址链
4.在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据

53、mysql聚簇和非聚簇索引的区别

·聚簇索引:将数据存储与索引放到了一块、并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的
非聚簇索引:叶子节点不存储数据、存储的是数据行地址,也就是说根据索引查找到数据行的位置再取磁盘查找数据,这个就有点类似一本树的目录,比如我们要找第三章第一节,那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章

优势:
1、查询通过聚皎索引可以直接获收数据,相比非聚簇索引需要第二次查询〈非覆盖索引的情况下)效率要高
2、聚族索引对于范查询的效率很高,因为其数据是按照大小排列的
3、聚族索引适合用在排序的场合,非聚族索引不适合
劣势:
1、维护索引很昂贵,特别是插入新行或者主键被更新导至要分员(page sp1it)的时候。建议在大量插入新行后,选在负获较低的时间段,通过OPTIMIZE TABLE优化表,因为必须被移动的行数据可能造成碎片。使用独亭表空间可以弱化碎片
2、表因为使用UUId(例机ID)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有比全表扫面更慢,所以建议使用int的auto_increment作为主键
3、如果主键比较大的话,那辅助索引将会变的更大,因为物助索引的叶子存储的是主键值;过长的主键值,公导致非叶子节点占用占用更多的物理空间

InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引,则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值

InnoDB中一定有主键,主键一定是聚簇索引,不手动设置、则会使用unique索引,没有unique索引,则会使用数据库内部的一个行的隐藏id来当作主键索引。在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值

MyISAM使用的是非聚簇索引,没有聚簇索引,非聚簇索引的两模B+树看上去没什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引B+树的节点存储了主键,辅助键索引B+树存储了辅助键。表数据存储在独立的地方,这两颗B+树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别。由于索引树是独立的,通过辅助键检系无需访问主键的索引树。
如果涉及到大数据量的排序、全表扫描、count之类的操作的话,还是MyISAM占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。

如果涉及到大数据量的排序、全表扫描、count之类的操作的话,还是MyISAM占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。

54、mysql索引的数据结构,各自优劣

索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等,InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
B+树:
B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且同层级的节点间有指针相互链接。在B+树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。因此,B+树索引被广泛应用于数据库、文件系统等场景。
哈希索引:
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快
如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值;前提是键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据;如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检系;
哈希索引也没办法利用索引完成排序,以及like 'xx%′这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询)
哈希索引也不支持多列联合索引的最左匹配规则;
B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量重复犍值情况下,哈希索引的效率也是极低的,因为存在哈希碰撞问题。

55、索引的设计原则

查询更快、占用空间更小
1.适合索引的列是出现在where子句中的列,或者连接子句中指定的列
2基数较小的类,索引效果较差,没有必要在此列建立索引
3.使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间,如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
4.不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
5.定义有外键的数据列一定要建立索引。
6.更新频繁字段不适合创建索引
7.若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
8.尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
9.对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
10.对于定义为text、image和bit的数据类型的列不要建立索引.

56、mysql锁的类型有那些

基于锁的属性分类:共享锁、排他锁
基于锁的粒度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB引擎)、记录锁、间隙锁、临键锁。
基于锁的状态分类:意向共享锁、意向排它锁。

  • 共享锁(Share Lock)

共享谈又称读锁,简称S谈:当一个事务为数据加上读领之后,其他事务只能对该数据加i读锁,而不能对数据加写镇,直到所有的读锁释放之后其他事务才能对其进行加持写谈。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。

  • 排他锁(exclusive Lock)

排他谈又称写锁,简称X谈;当一个事务为数据加上写锁时,其他请求将不能再为数据加任何镜,直到该顿释放之后,其他事务才能对数据进行加谈。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读收。避免了出现脏数器和脏读的问题。

  • 表锁

表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问:

  • 行锁

行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被镜住的记录不能访问,其他的记录可正常访向
特点:粒度小,加谈比表谈麻烦,不容易冲突,相比表谈支持的并发要高:

  • 记录锁(Record Lock)

记承谈也属于行谈中的一种,只不过记录锁的范围只是表中的某一条记录,记录谈是说事务在加锁后谈住的只是表的某一条记录。
精准条件命中,并且命中的条件字段是唯一索引
加了记录镜之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。

  • 页锁

页级谈是MySQL中锁定粒度介于任级镜和表级镜中间的一种谈。表级镜速度快,但冲突多,行级冲突少,但速度慢。所以取了折裘的页级,一次馍定相邻的一组记录。
特点:开销和加锁时间界于表镜和行锁之问:会出现死锁;锁定粒度界于表锁和行锁之问,并发度一般

  • 间隙锁(Gap Lock)

属于行锁中的一种,向陬谈是在事务加锁后其镇住的是表记录的某一个区间,当表的相邻ID之间出现空隙则会形成一个区间,遵循左开右闭原则。
范围查询并且查询未命中记录,查询条件必须命中索引、问隙谈只会出现在REPEATABLE_READ(重复读)的事务级别中。
触发条件:防止:幻]读问题,事务并发的时候,如果没有问隐锁,就会发生如下图的问题,在同一个事务里,A事务的两次查询出的结果会不一样。
比如表里面的数据TD为1,4,5,7,10 ,那么会形成以下几个间隙区间,-n-1区间,1-4区间,7-10区间,10-n区问(-n代表负无穷大,n代表正无穷大》

  • 临建锁(Next-Key Lock)

也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录镜和问隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有问隐空间也会谈住,再之它会把相邻的下一个区间也会锁住
触发条件:范围查询并命中,查询命中了索引。
结合记录谈和向陈锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之后,在范围区向内数据不允许被修改和插入

如果当事务A加锁成功之后就没置一个状态告诉后面的人,已经有人对表里的行加了一个排他锁了,你们不能对整个表加共享锁或排它锁了,那么后面需要对整个表加锁的人只需要获取这个状态就知道自己是不是可以对表加锁,避免了对整个索引树的每个节点扫描是否加锁,而这个状态就是意向锁。

  • 意向共享锁

当一个事务试图对整个表进行加共亭谈之前,首先需要获得这个表的意向共亭谈。

  • 意向排他锁

当一个事务试图对整个表进行加排它镜之前,首先需要获得这个表的意向排它谈。

57、mysql执行计划怎么看

执行计划就是sql的执行查询的顺序,以及如何使用索引查询,返回的结果集的行数EXPLAIN SELECT * from A where x=? and Y=?


image.png

1.id :是一个有顺序的编号,是查询的顺序号,有几个select就显示几行。id的顺序是按select 出现的顺序增长的。id列的值越大执行优先级越高越先执行,id列的值相同则从上往下执行,id列的值为NULL最后执行。
2.selectType表示查询中每个select子句的类型

  • SIMPLE:表示此查询不包含UNION查询或子查询.PRIMARY:表示此查询是最外层的查询(包含子查询)- SuBQUERY:子查询中的第一个SELECT
  • UNION:表示此查询是 UNION的第二或随后的查询
  • DEPENDENT UNION: UNION 中的第二个或后面的查询语句,取决于外面的查询 uNION RESULT,UNION的结果
  • DEPENDENT SUBQUERY:子查询中的第一个SELECT,取决于外面的查询.即子查询依赖于外层查询的结果. DERIVED:衍生,表示导出表的SELECT (FROM子句的子查询)

3.table:表示该语句查询的表
4.type:优化sql的重要字段,也是我们判断sal性能和优化程度重要指标。他的取值类型范围:

  • const:通过索引一次命中,匹配一行数据
  • system:表中只有一行记录,相当于系统表;
  • eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配ref:非唯一性索引扫描,返回匹配某个值的所有
  • range:只检索给定范围的行,使用一个索引来选择行,一般用于between、<、>; index:只遍历索引树;
  • ALL:表示全表扫描,这个类型的查询是性能最差的查询之一。那么基本就是随着表的数量增多,执行效率越慢。

执行效率:
ALL < index

5.possible_keys:它表示Mysql在执行该sqli语句的时候,可能用到的索引信息,仅仅是可能,实际不一定会用到。
6.key:此字段是 mysql在当前查询时所真正使用到的索引。他是possible_keys的子集

58、事务的基本特性和隔离级别

事务基本特性ACID分别是:
原子性指的是一个事务中的操作要么全部成功,要么全部失败。
一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设A只有90块,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证
隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。
隔离性有4个隔离级别,分别是:

  • read uncommit读未提交,可能会读到其他事务未提交的数据,t也叫做脏读
    用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。
  • read commit读已提交,两次读取结果不一致,叫做不可重复读。不可重复读解决了脏读的问题,他只会读取已经提交的事务
    用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同
  • repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
  • serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题

59、关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?

在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么?是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?

所以优化也是针对这三个方向来的

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
  • 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

60、ACID靠什么保证的?

A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql

C一致性由其他三大特性保证、程序代码要保证业务上的一致性

l隔离性由MVCC来保证

D持久性由内存+redo log来保证,mysq|修改数据同时在内存和redo log记录这次操作,宕机的时候可以从redolog恢复

InnoD8 redo log写盘,InnoDB事务进入prepare状态。
如果前面 prepare 成功,binlog 写盘,再继续将事务日志持久化到 bin1og,如果持久化成功,那么、InnoDB事务则进入commit状态(在 redo log里面写一个commit记录)

redolog的刷盘会在系统空闲时进行

61、什么是MVCC

多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据,版本链

MVCC只在 READCOMMITED和REPEATABLE READ两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁,

聚簇索引记录中有两个必要的隐藏列:

trx_id:用来存储每次对某条聚簇索引记录进行修改的时候的事务id.

roll_pointer:每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本)

已提交读和可重复读的区别就在于它们生成Readview的策略不同。


image.png

开始事务时创建readview,readView维护当前活动的事务id,即未提交的事务id,排序生成一个数组访问数据,获取数据中的事务id(获取的是事务id最大的记录),对比readview:

如果在readview的左边(比readview都小),可以访问(在左边意味着该事务已经提交)

如果在readview的右边(比readview都大)或者就在readview中,不可以访问,获取roll_pointer,取上一版本重新对比(在右边意味着,该事务在readview生成之后出现,在readview中意味着该事务还未提交)

已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个Readview,之后的读都复用之前的Readview。

这就是Mysql的MVCC,通过版本链,实现多版本,可并发读·写,写·读。通过ReadView生成策略的不同实现不同的隔离级别。

62、mysql主从同步原理

mysql主从同步的过程:
Mysql的主从复制中主要有三个线程:master(binlog duno thread) . slave(I/o thread . sQLthread, Master—条线程和Slave中的两条线程。

  • 主节点binlog,主从复制的基础是主库记录数据库的所有变更记录到 binlog。binlog是数据库服务器启动的那一刻起,保存所有修改数据库结构或内容的一个文件。
  • 主节点 log dump线程,当binlog有变动时,log dump线程读取其内容并发送给从节点。
  • 从节点VO线程接收binlog内容,并将其写入到relay log文件中。
  • ·从节点的SQL线程读取relay log文件内容对数据更新进行重放,最终保证主从数据库的一致性。

注:主从节点使用 binglog文件+ position偏移量来定位主从同步的位置,从节点会保存其已接收到的偏移量,如果从节点发生宕机重启,则会自动从position 的位置发起同步。

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

全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。

半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成,

63、简述MyISAM和InnoDB的区别

MyISAM:

  • 不支持事务,但是每次查询都是原子的
  • 支持表级锁,即每次操作是对整个表加锁;
  • 存储表的总行数;
  • 一个MYISAM表有三个文件:索引文件、表结构文件、数据文件;
  • 采用非聚集索引,索引文件的数据域存储指向数据文件的指针。辅索引与主索引基本一致,但是辅索引不用保证唯一性。

lnnoDb:

  • 支持ACID的事务,支持事务的四种隔离级别;
  • 支持行级锁及外键约束:因此可以支持写并发;
  • 不存储总行数;
  • 一个InnoDb引擎存储在一个文件空间(共享表空间,表大小不受操作系统控制,一个表可能分布在多个文件里),也有可能为多个(设置为独立表空,表大小受操作系统文件大小限制,一般为2G),受操作系统文件大小的限制;
  • 主键索引采用聚集索引(索引的数据域存储数据文件本身),辅索引的数据域存储主键的值;因此从辅索引查找数据,需要先通过辅索引找到主键值,再访问辅索引;最好使用自增主键,防止插入数据时,为维持B+树结构,文件的大调整。

64、简述mysql中索引类型及对数据库的性能的影响

  • 普通索引:允许被索引的数据列包含重复的值。

  • 唯一索引:可以保证数据记录的唯一性。

  • 主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字PRIMARY KEY 来创建。

  • 联合索引:索引可以覆盖多个数据列,如像INDEX(columnA, columnB)索引。

  • 全文索引:通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引

  • 索引可以极大的提高数据的查询速度。

  • 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

  • 但是会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件

  • 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚集索引很多,一旦聚集索引改变,那么所有非聚集索引都会跟着变。

65、RDB和AOF机制

RDB: Redis DataBase
在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。

优点:
1、整个Redis数据库将只包含—个文件dump.rdb,方便持久化。
2、容灾性好,方便备份。
3、性能最大化,fork子进程来完成写操作,让主进程继续处理命令,所以是IO最大化。使用单独子进程来进行持久化,主进程不会进行任何IO操作,保证了redis 的高性能
4.相对于数据集大时,比AOF的启动效率更高。

缺点:
1、数据安全性低。RDB是间隔一段时间进行持久化,如果持久化之间redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候)
2、由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致整个服务器停止服务几百亳秒,甚至是1秒钟。

AOF:Append Only File
以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录,可以打开文件看到详细的操作记录

优点:
1、数据安全,Redis中提供了3中同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,真效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。
2、通过append模式写文件,即使中途服务器宕机也不会破坏已经存在的内容,可以通过redis-check-aof 工具解决数据一致性问题。
3、AOF机制的rewrite模式。定期对AOF文件进行重写,以达到压缩的目的

缺点:
1、AOF文件比 RDB文件大,且恢复速度慢.
2、数据集大的时候,比rdb启动效率低。
3、运行效率没有RDB高

AOF文件比RDB更新颜率高,优先使用AOF还原数据。
AOF比RDB更安全也更大
RDB性能比AOF好
如果两个都配了优先加载AOF

66、Redis的过期键的删除策略

Redis是key-value数据库,我们可以设置Redis中缓存的key的过期时间。Redis的过期策略就是指当Redis中缓存的key过期了,Redis如何处理。

  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

(expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的亳秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

Redis中同时使用了惰性过期和定期过期两种过期策略。

67、Redis线程模型、单线程快的原因

Redis基于Reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器file event handler。这个文件事件处理器,它是单线程的,所以Redis 才叫做单线程的模型,它采用IO多路复用机制来同时监听多个Socket,根据Socket上的事件类型来选择对应的事件处理器来处理这个事件。可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了 Redis 内部的线程模型的简单性。

文件事件处理器的经钩包含4个部分:多个Socket、IO多路复用程序、文件事件分派器以及事件处理器(命令请求处理器、命令回复处理器、连接应答处理器等)。

多个Socket可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个Socket,会将Socket放入一个队列中排队,每次从队列中取出一个Socket给事件分派器,事件分派器把Socket给对应的事件处理器。

然后一个Socket的事件处理完之后,IO多路复用程序才会将队列中的下一个Socket给事件分派器。文件事件分派器会根据每个Socket当前产生的事件,来选择对应的事件处理器来处理。

单线程快的原因:
1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换带来的性能问题

68、缓存雪崩、缓存穿透、缓存击穿

缓存雪崩是指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存。
  • 缓存预热
  • 互斥锁

缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案:

  • 接口层增加校验,如用户鉴权校验,id做基础校验,id<=O的直接拦截;
  • 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
  • 采用布降过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap中,一个一定不存在的数据会被这个bitmap栏战掉.从而游免了对底食存储系结的查询压力

d

你可能感兴趣的:(javq学习)