java面试题大全

一、jvm面试题

1.jvm的内存结构

(1)类加载器、运行时数据区、执行引擎、本地库接口
在这里插入图片描述

(2).运行时数据区组成:方法区、堆、虚拟机栈、本地方法栈、程序器计数器
在这里插入图片描述

(3)虚拟机栈结构:局部变量表、操作数栈、动态链接、完成出口
在这里插入图片描述

2.垃圾回收期CMS和G1的区别?

CMS垃圾回收器是一种以获取最短回收停顿时间为目标的垃圾回收器
在这里插入图片描述

CMS垃圾回收器是基于”标记–清除”(Mark-Sweep)算法实现的,整个过程分为四个步骤:
过程:

  1. 初始标记:独占PUC,stop-the-world, 仅标记GCroots能直接关联的对象
  2. 并发标记:可以和用户线程并发执行,通过GCRoots Tracing 标记所有可达对象。
  3. 重新标记:独占CPU,stop-the-world, 对并发标记阶段用户线程运行产生的垃圾对象进行标记修正,以及更新自我拯救那部分逃逸对象
  4. 并发清理:可以和用户线程并发执行,清理垃圾

优点:
并发,低停顿
缺点:
5. CPU敏感:在并发阶段虽然不会导致用户线程停顿,但是会因为占用了一部分线程使应用程序变慢
6. 浮动垃圾:在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾
7. 空间碎片:CMS使用“标记-清理”法会产生大量的空间碎片,当碎片过多,将会给大对象空间的分配带来很大的麻烦,往往会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次FullGC,为了解决这个问题CMS提供了一个开关参数,用于在CMS顶不住,要进行FullGC时开启内存碎片的合并整理过程,但是内存整理的过程是无法并发的,空间碎片没有了但是停顿时间变长了

CMS 出现FullGC的原因:

  1. 年轻代晋升到老年代没有足够的连续空间,很有可能是内存碎片导致的
  2. 在并发过程中JVM觉得在并发过程结束之前堆就会满,需要提前触发FullGC

G1:是一款面向服务端应用的垃圾回收器
整个过程分为四个步骤:

  • 初始标记(stop the world事件 CPU停顿只处理垃圾);
  • 并发标记(与用户线程并发执行);
  • 最终标记(stop the world事件 ,CPU停顿处理垃圾);
  • 筛选回收(stop the world事件 根据用户期望的GC停顿时间回收)
    G1只有并发标记阶段能做到用户线程和回收线程并发执行
    在这里插入图片描述

特点:

  1. 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1回收器仍然可以通过并发的方式让java程序继续执行。
  2. 分代收集:分代概念在G1中依然得以保留。虽然G1可以不需要其它回收器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代了。
  3. 空间整合,没有内存碎片产生:由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片
    在最后筛选回收阶段,对每个region里的回收对象价值(回收该区域的时间消耗和能得到的内存比值)最后进行排序,用户可以自定义停顿时间,那么G1就可以对部分的region进行回收!这使得停顿时间是用户自己可以控制的

与其它回收器相比,G1变化较大的是它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留了新生代和来年代的概念,但新生代和老年代不再是物理隔离的了它们都是一部分Region(不需要连续)的集合。同时,为了避免全堆扫描,G1使用了Remembered Set来管理相关的对象引用信息。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆扫描也不会有遗漏了。
最后筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划(可预测的停顿),这一过程同样是需要停顿线程的,但Sun公司透露这个阶段其实也可以做到并发,但考虑到停顿线程将大幅度提高收集效率,所以选择停顿

3.GC Roots都有哪些对象?

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

4.内存溢出和内存泄漏的区别?

(1)什么情况下会发生栈内存溢出 ?

  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果
  2. 新建线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)

(2)内存泄漏和内存溢出的区别

  1. 内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
  2. 内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

5.双亲委托模型?

在这里插入图片描述

从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:

  1. 启动类加载器(Bootstrap ClassLoader),使用 C++ 实现,是虚拟机自身的一部分
  2. 所有其它类的加载器,使用 Java 实现,独立于虚拟机,并且全部继承自抽象类 java.lang.ClassLoader。

从 Java 开发人员的角度看,类加载器可以划分得更细致一些:

  • 启动类加载器(Bootstrap ClassLoader):前面已经大致介绍过了,这个类加载器负责将存放在 <JRE_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
  • 扩展类加载器(Extension ClassLoader):这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

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

(1)双亲委托模型的优势

  1. 采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
  2. 其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

(2)怎么打破双亲委派模型?
spi : jdbc
OSGi:模块化热部署

(3)JDBC怎么打破双亲委派模型的?
原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的mysql-connector-.jar中的Driver类具体实现的。 原生的JDBC中的类是放在rt.jar包的,是由启动类加载器进行类加载的,在JDBC中的Driver类中需要动态去加载不同数据库类型的Driver类,而mysql-connector-.jar中的Driver类是用户自己写的代码,那启动类加载器肯定是不能进行加载的,既然是自己编写的代码,那就需要由应用程序启动类去进行类加载。于是乎,这个时候就引入线程上下文类加载器(Thread Context ClassLoader)。有了这个东西之后,程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。

二、多线程

1.线程的状态

Thread源码中有个枚举State,总共定义了线程的六种状态:

public enum State {
      
        NEW,  //新建状态,线程对象已经创建,但尚未启动

        RUNNABLE, //就绪状态,可运行状态,调用了线程的start方法,已经在java虚拟机中执行,等待获取操作系统资源如CPU,操作系统调度运行。

        BLOCKED, //堵塞状态。线程等待锁的状态,等待获取锁进入同步块/方法或调用wait后重新进入需要竞争锁

        WAITING, //等待状态。等待另一个线程以执行特定的操作。调用以下方法进入等待状态。 Object.wait(), Thread.join(),LockSupport.park

        TIMED_WAITING, //线程等待一段时间。调用带参数的Thread.sleep, objct.wait,Thread.join,LockSupport.parkNanos,LockSupport.parkUntil

        TERMINATED; //结束状态。
    }

堵塞状态(Blocked):
实例:小亮要骑车去上班,结果发现自行车被锁住了,现在小亮无法去上班了,就是处于堵塞状态了。

进入堵塞状态的方法:

  1. 当一个线程要进入synchronized语句块/方法时,如果没有获取到锁,会变成BLOCKED
  2. 在调用Object.wait()后,被notify()唤醒,再次进入synchronized语句块/方法时,如果没有获取到锁,会变成BLOCKED

不限期等待状态(waiting):
实例:小亮喜欢小红,知道她也去上班,就在楼下等着她一起去上班(小红不下来,他就不走)

进入不限期等待状态的方法:

  1. 没有设置timeout参数的Object.wait()
  2. 没有设置timeout参数的Thread.join()
  3. LockSupport.park()

限期等待状态(timed_waiting):
实例:小亮知道小红也去上班,就打算等她半小时,结果她还没有下来,于是小亮决定不等她了,直接去公司了。

进入限期等待的方法:

  1. Thread.sleep()方法
  2. 设置了timeout参数的Object.wait()方法
  3. 设置了timeout参数的Thread.join()方法
  4. LockSupport.parkNanos()方法
  5. LockSupport.parkUntil()方法

2.sleep和wait的区别

sleep与wait差异总结

  1. 来自不同的类:sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
  2. 有没有释放锁(释放资源):sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。
  3. 一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
  4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

3.volatile的特性和原理

特性:

  1. 可见性
  2. 禁止指令重排序

现象:
被volatile修饰的关键字,当发生修改后立即刷新到主内存,当其他cpu需要读取这个volatile变量时设置本地缓存中该变量地址无效,从主内存重新读取,实现了内存可见性
原理:
编译器在编译时发现volatile,插入lock(即内存屏障)前缀指令,而lock有以下2个特性

  1. 写变量后会强制刷新回主内存
  2. 读该变量时会直接从主内存读取

不足:无法保证原子性&安全性
volatile无法保证原子性,因为对于一个变量的读操作,可以解释为以下3部分

  1. 读取volatile变量到本地
  2. 修改变量值
  3. local值写会主内存

4.sychnozied的原理

(1)特性

  • 原子性
    ​ 原子性就是一个操作或者多个操作要么一起执行,要么不执行。(Volatile与Sychronized的最大区别就是不具备原子性)

  • 可见性
    ​ Sychronzied被加锁对象的锁对于每个线程都是可见的,都能获取到锁的状态,在释放锁之前,会把最新的值更新到主存,保证值都是最新状态。
    ​ Volatile修饰的变量,每当值要改变时,都会立即更新到主存中,主存的内容对于其他线程都是可见的,就能保证值都是最新状态的。

  • 有序性
    ​ Sychronized和Volatile都具有有序性,Java编译器允许指令重排,但指令重排不会影响单线程执行顺序,会影响多线程的并发执行顺序,加了锁后就保证每次都只有一个线程访问,保证有序性。

  • 可重入性(可重入锁)
    ​ Sychronized、ReentrantLock和Volatile都是可重入锁,当一个线程试图操作一个其他线程持有的对象锁的临界资源时(临界资源就是只能有一个线程使用的资源),那这个线程会处于阻塞状态。可重入就是同一个线程可以重复获取对象锁。可重入锁的作用是为了防止死锁,比如一个加锁的方法a调用另一个加锁的方法b,它们是用一个线程的执行代码,会在a那里拿到锁往下执行,然后b也要去获取对象锁才能执行,如果不能重入,则会在b这里形成死循环。

(2)底层实现

  • 偏向锁
    ​ 偏向锁就是当只有一个线程去执行同步代码块时,就是偏向锁,不存在多线程竞争,再次请求锁时,不需要申请,只是对比Mark Word中的锁标记是不是偏向锁和线程id是不是一致就可以,这样省去了大量锁申请的操作。

意义:锁偏向于第一个获得它的线程。如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

  • 轻量锁
    ​ 当有第二个线程去申请锁时,就会升级为轻量锁。其他线程会通过自旋的方式去申请锁,自旋锁的实现方式是CAS,通过循环不断的申请,但会耗费CPU时间片(循环过程中线程会一直处于running状态,但是基于JVM的线程调度,升级为阻塞锁,会出让时间片,所以其他线程依旧有申请锁和释放锁的机会,下面会说自旋锁的优化),阻塞锁会放弃CPU时间片但在上下文的切换会存在消耗,所以在临界区小的时候采用自旋的方式是效率比较高的,当临界区大的时候,自旋就会随着时间的推移产生很多性能问题。

​ 当自旋获取锁失败时,说明有其他的线程也在竞争锁,则会升级为重量锁。

  • 重量锁
    ​ 重量级锁是由轻量级锁升级而来,当同一时间有多个线程竞争锁时,锁就会被升级成重量级锁,此时其申请锁带来的开销也就变大。

​ 重量级锁一般使用场景会在追求吞吐量,同步块或者同步方法执行时间较长的场景。

5.线程池

(1)线程池的类型

  • newCachedThreadPool
    创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
    这种类型的线程池特点是:
    工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
    如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
    在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统OOM。
  • newFixedThreadPool
    创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
    FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
  • newSingleThreadExecutor
    创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
  • newScheduleThreadPool
    创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。
  • newSingleThreadScheduledExecutor
    创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。线程池中最多执行1个线程,之后提交的线程活动将会排在队列中以此执行并且可定时或者延迟执行线程活动。

(2)线程池的参数

  • corePoolSize 线程池核心线程大小
    线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。

  • maximumPoolSize 线程池最大线程数量
    一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。

  • keepAliveTime 空闲线程存活时间
    一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定

  • unit 空闲线程存活时间单位
    keepAliveTime的计量单位

  • workQueue 工作队列
    新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:

①ArrayBlockingQueue
基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

②LinkedBlockingQuene
基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而不会去创建新线程直到maxPoolSize,因此使用该工作队列时,参数maxPoolSize其实是不起作用的。

③SynchronousQuene
一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。

④PriorityBlockingQueue
具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

  • threadFactory 线程工厂
    创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等

  • handler 拒绝策略
    当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
    ①CallerRunsPolicy:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
    ②AbortPolicy:该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
    ③DiscardPolicy:该策略下,直接丢弃任务,什么都不做。
    ④DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列

三、mysql面试题

1.InnoDB和MyISAM的区别?

  1. InnoDB支持事务,MyISAM不支持事务
  2. InnoDB支持外键,而MyISAM不支持。
  3. InnoDB是聚集索引,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
    MyISAM是非聚集索引,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。

Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI
Innodb:frm是表定义文件,ibd是数据文件
Myisam:frm是表定义文件,myd是数据文件,myi是索引文件

  1. InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件)

  2. MyISAM表格可以被压缩后进行查询操作

  3. InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁
    7、InnoDB表必须有唯一索引(如主键)(用户没有指定的话会自己找/生产一个隐藏列Row_id来充当默认主键),而Myisam可以没有

2.MySQL的索引类型?

普通索引:仅加速查询
唯一索引:加速查询 + 列值唯一(可以有null)
主键索引:加速查询 + 列值唯一(不可以有null)+ 表中只有一个
组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并
全文索引:对文本的内容进行分词,进行搜索

3.MySQL的事务特性?

ACID
1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
   2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
   3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
   4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

4.MySQL的事务隔离级别?

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted) 是 是 是
读已提交(read-committed) 否 是 是
可重复读(repeatable-read) 否 否 是
串行化(serializable) 否 否 否
在RR的隔离级别下,Innodb使用MVCC和next-key locks解决幻读,MVCC解决的是普通读(快照读)的幻读,next-key locks解决的是当前读情况下的幻读。

5.MySQL优化

1.使用索引
2.避免索引失效:最左前缀法则、不使用不等于 != <> 、is null /is not null 、不使用or、避免子查询使用join

6.#{}和${}的区别
#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by “111”, 如果传入的值是id,则解析成的sql为order by “id”.
  
2. $将传入的数据直接显示生成在sql中。如:order by u s e r i d user_id userid,如果传入的值是111,那么解析成sql时的值为order by user_id, 如果传入的值是id,则解析成的sql为order by id.
  
3. #方式能够很大程度防止sql注入。
  
4.$方式无法防止Sql注入。

5.KaTeX parse error: Expected 'EOF', got '#' at position 32: …传入表名.    6.一般能用#̲的就别用.

7.乐观锁、悲观锁、什么情况下锁失效?

乐观锁:使用版本号机制或CAS操作实现
悲观锁:for update

锁失效:
1.当 Where 查询条件中的字段没有索引时,更新操作会锁住全表

8.mysql语句执行过程?

MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
SQL 等执行过程分为两类,一类对于查询等过程如下:权限校验—》查询缓存—》分析器—》优化器—》权限校验—》执行器—》引擎
对于更新等语句执行流程如下:分析器----》权限校验----》执行器—》引擎—redo log prepare—》binlog—》redo log commit

redo log是支持事务的持久性的。undo log支持事务的一致性

四、Redis

1.redis的数据类型:string、hash、list、set、zset

2.redis常用命令

String : 常用命令: set,get,decr,incr,mget 等。
Hash: 常用命令:hget,hset,hgetall 等。
List: 常用命令:lpush,rpush,lpop,rpop,lrange等
Set: 常用命令:sadd,spop,smembers,sunion 等。
zset: 常用命令:zadd,zrange,zrem,zcard等

3.hgetAll推不推荐使用

Redis提供了接口(hgetall)可以直接取到全部的属性数据,但是如果内部Map的成员很多,那么涉及到遍历整个内部Map的操作,由于Redis单线程模型的缘故,这个遍历操作可能会比较耗时,而另其它客户端的请求完全不响应,这点需要格外注意

4.redis的持久化,RDB,AOF,区别,各自的特点是什么,适合什么场景

RDB持久化机制
对Redis中的数据执行周期性的持久化

RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,若过程中Redis宕机,那么就会丢失最近未持久化的数据

AOF机制
将每条写命令作为日志,以append-only模式写入一个日志文件,在Redis重启时,通过回放日志中的写入指令来重构整个数据

5.Redis怎么实现分布式锁?

Redis要实现分布式锁,以下条件应该得到满足:
互斥性:在任意时刻,只有一个客户端能持有锁。
不能死锁:客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

可以直接通过 set key value px milliseconds nx 命令实现加锁, 通过Lua脚本实现解锁。
//获取锁(unique_value可以是UUID等)
SET resource_name unique_value NX PX 30000

//释放锁(lua脚本中,一定要比较value,防止误解锁)
if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end

代码解释
set 命令要用 set key value px milliseconds nx,替代 setnx + expire 需要分两次执行命令的方式,保证了原子性,
value 要具有唯一性,可以使用UUID.randomUUID().toString()方法生成,用来标识这把锁是属于哪个请求加的,在解锁的时候就可以有依据;
释放锁时要验证 value 值,防止误解锁;
通过 Lua 脚本来避免 Check And Set 模型的并发问题,因为在释放锁的时候因为涉及到多个Redis操作 (利用了eval命令执行Lua脚本的原子性);

首先,set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性。其次,由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁。最后,因为我们将value赋值为requestId,用来标识这把锁是属于哪个请求加的,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端。

五、zookeeper

1.zookeeper选举过程

Leader选举是保证分布式数据一致性的关键所在。当Zookeeper集群中的一台服务器出现以下两种情况之一时,需要进入Leader选举。
  (1) 服务器初始化启动。
  (2) 服务器运行期间无法和Leader保持连接。
  下面就两种情况进行分析讲解。
  1. 服务器启动时期的Leader选举
  若进行Leader选举,则至少需要两台机器,这里选取3台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器Server1启动时,其单独无法进行和完成Leader选举,当第二台服务器Server2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程如下
  (1) 每个Server发出一个投票。由于是初始情况,Server1和Server2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时Server1的投票为(1, 0),Server2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
  (2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
  (3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行PK,PK规则如下
    · 优先检查ZXID。ZXID比较大的服务器优先作为Leader。
    · 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。
  对于Server1而言,它的投票是(1, 0),接收Server2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时Server2的myid最大,于是更新自己的投票为(2, 0),然后重新投票,对于Server2而言,其无须更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
  (4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于Server1、Server2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了Leader。
  (5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。
  
2. 服务器运行时期的Leader选举
  在Zookeeper运行期间,Leader与非Leader服务器各司其职,即便当有非Leader服务器宕机或新加入,此时也不会影响Leader,但是一旦Leader服务器挂了,那么整个集群将暂停对外服务,进入新一轮Leader选举,其过程和启动时期的Leader选举过程基本一致。假设正在运行的有Server1、Server2、Server3三台服务器,当前Leader是Server2,若某一时刻Leader挂了,此时便开始Leader选举。选举过程如下
  (1) 变更状态。Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
  (2) 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定Server1的ZXID为123,Server3的ZXID为122;在第一轮投票中,Server1和Server3都会投自己,产生投票(1, 123),(3, 122),然后各自将投票发送给集群中所有机器。
  (3) 接收来自各个服务器的投票。与启动时过程相同。
  (4) 处理投票。与启动时过程相同,此时,Server1将会成为Leader。
  (5) 统计投票。与启动时过程相同。
  (6) 改变服务器的状态。与启动时过程相同。

六、消息中间件:kafka、MQ

1.kafka如何保证消息顺序消费?

kafka的顺序消息仅仅是通过partitionKey,将某类消息写入同一个partition,一个partition只能对应一个消费线程,以保证单个partition数据有序

1.producer发消息到队列时,通过加锁保证有序
现在假设两个问题
broker leader在给producer发送ack时,因网络原因超时,那么Producer 将重试,造成消息重复。
先后两条消息发送。t1时刻msg1发送失败,msg2发送成功,t2时刻msg1重试后发送成功。造成乱序。
2.解决重试机制引起的消息乱序
为实现Producer的幂等性,Kafka引入了Producer ID(即PID)和Sequence Number。对于每个PID,该Producer发送消息的每个<Topic, Partition>都对应一个单调递增的Sequence Number。同样,Broker端也会为每个<PID, Topic, Partition>维护一个序号,并且每Commit一条消息时将其对应序号递增。对于接收的每条消息,如果其序号比Broker维护的序号)大一,则Broker会接受它,否则将其丢弃:
如果消息序号比Broker维护的序号差值比一大,说明中间有数据尚未写入,即乱序,此时Broker拒绝该消息,Producer抛出InvalidSequenceNumber
如果消息序号小于等于Broker维护的序号,说明该消息已被保存,即为重复消息,Broker直接丢弃该消息,Producer抛出DuplicateSequenceNumber
Sender发送失败后会重试,这样可以保证每个消息都被发送到broker

2.kafka如何保证生产者消息不丢失?

生产者消息发送producer.send(Object msg) 是异步的,
永远使用带有返回值值的消息发送方式,即 producer.send(msg,callback)
通过callback可以准确的告诉你消息是否发送成功了,发送失败了你也可以有处置方法;
网络抖动: 重发
发送消息超出大小:调整消息大小进行发送

3.kafka如何保证消费者消息不丢失?

关闭自动提交offset,处理完之后受到移位

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值