1. 面向对象

  • 封装:明确标识出允许外部使用的所有成员函数和数据项,外部调用无需修改或者关心内部实现
  • 继承:继承基类的方法,并作出自己的改变或扩展,子类共性的方法或者属性直接使用父类的,而不需要自己定义。
  • 多态:基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同,条件继承或者重写  父类类型 变量名 = new 子类对象    。一个父类类型可以调用它多个不同子类里的方法。

1.x 成员变量(实例变量)、静态变量(类变量)、局部变量的区别

  • 成员变量(实例变量):针对所在类有效,随对象的创建而存在,随对象消失而消失,存储在堆内存中
  • 局部变量:只在方法内的某一部分有效,在方法被调用或者语句被执行时存在,存储在栈中,没有默认初始值,使用前必须赋值
  • 静态变量(类变量):只要加载了这个类,静态变量就存在,随着类消失而消失,静态变量是所有对象共享的数据,存储在方法区中,可以通过类名调用,也可以通过对象名调用。 

1.x TCP协议

  四层协议:

(1)链路层,有时也称作数据链路层或网络接口层,通常包括操作系统中的设备驱动程序和计算机中对应的网络接口卡。

(2)网络层,有时也称作互联网层,处理分组在网络中的活动。网络层协议包括IP协议(网际协议),ICMP协议(internet互联网控制报文协议),以及IGMP协议(Internet组管理协议)

(3)运输层,包含协议TCP(传输控制协议)和UDP(用户数据报协议)。TCP把数据分成小块,交给网络层。UDP则为应用层提供服务,把数据报的分组从一台主机发送到另一台主机,但并不保证发送到另一台主机。

(4)应用层负责处理特定的应用程序细节。Telnet远程登录,FTP文件传输协议,SMTP简单邮件传送协议,SNMP简单网络管理协议。

  建立TCP的三次握手

  • 第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)
  • 第二次握手:服务器收到syn包,必须确认客户端的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。
  • 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

  四次挥手过程:

(1) TCP客户端发送一个FIN报文,用来关闭客户到服务器的数据传送。

(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

(3) 服务器关闭客户端的连接,发送一个FIN给客户端。

(4) 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。

2.JDK、JRE、JVM三者区别和联系

  •         JDK:Java Development Kit    java开发工具
  •         JRE:Java Runtime Environment      java运行时环境
  •         JVM    java Virtual Machine      java虚拟机

3. ==和equals

    ==对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址,比如 int i=1,int j=1,此时i==j返回为true。如果String i =  new String("abc"),String j =  new String("abc"),此时i==j返回为false。

4.String、StringBuffer、StringBuilder区别及使用场景

  • String是final修饰的,不可变,每次操作都会产生新的String对象
  • StringBuffer和StringBuilder都是在原对象上操作
  • StringBuffer是线程安全的,SpringBuilder是线程不安全
  • StringBuffer方法都是synchronize修饰的(所有线程安全)
  • 性能: StringBuilder > StringBuffer > String

场景:经常需要改变字符串内容时使用后面两个

        优先使用StringBuilder,多线程使用共享变量时使用StringBuffer

5.接口和抽象类的区别

  • 抽象类可以存在普通成员函数,而接口中只能存在public abstract方法
  • 抽象类中的成员量可以是各种类型的,而接口中的成员变量只能是public static final类型的
  • 抽象类只能继承一个,接口可以实现多个

5.x. 常用集合汇总

 

 

 

6.List和Set的区别

  • List 保存有序,可重复的对象,并且运行有多个Null元素对象;Set无序,不可重复,最多允许一个Null元素对象
  • list、set、map都是线程不安全的

7.ArrayList和LinkedList区别

      ArrayList:基于动态数组,连续内存存储,适合下标访问(随机访问),扩容机制。因为数组长度固定,超出长度长度存数据时需要新建数组,然后将老数组的数据拷贝到新数组中,如果不是尾插法还会涉及到元素的移动(往后复制一份,插入新元素),使用尾插法并指定初始化容量可以极大提升性能。

      LinkedList:基于链表,可以存储在分散的内存中,适合做数据的插入及删除,不适合查询(需要逐一遍历)。遍历LinkedList时必须使用迭代器,不能用for循环,因为每一次for循环取得某一元素时都需要对list重新进行遍历,性能消耗极大。

8.HashMap和HashTable的区别?底层实现是什么?

  • HashMap方法没有synchronize修饰,线程不安全,HashTable线程安全
  • HashMap运行key和value为null,而HashTable不允许

底层实现:数组加链表

jdk8开始,链表高度到8,数组长度超过64,链表转变为红黑树,元素以内部类Node节点存在

  • 计算key的hash值,通过hash值对数组长度按位与得到下标
  • 如果没有产生hash冲突(下标位置没有元素),则直接创建Node存入数组
  • 如果产生hash冲突,先equals比较,相同则取代该元素,不同,则判断链表高度插入链表,链表高度达到8,并且数组长度到64则转变为红黑树,长度低于6则将红黑树转回链表
  • key为null,存在下标0的位置

8.X. HashSet的底层实现原理

     HashSet实现了Set接口,但是底层是调用的HashMap,HashSet存储不了重复的数,而HashMap会把重复的数覆盖掉。在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,添加的值是底层HashMap的key,value是一个final修饰的object的常量。

8.X. 说一下ThreadLocal

  • ThreadLocal是java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法获取缓存的数据
  • ThreadLocal底层是通过ThreadLocalMap实现的,每个Thread对象中都存在一个ThreadLocalMap,Map的key是ThreadLocal对象,value是需要缓存的值
  • 如果在线程池中使用ThreadLocal会造成内存泄漏,因为ThreadLocal对象使用完后,应该把key和value进行回收,而线程池的线程不会回收,Entry(key和value)也不会回收,从而造成了内存泄漏,使用了ThreadLocal对象后,手动调用remove方法清除Entry对象。
  • ThreadLocal经典的使用场景是连接管理:每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection;

9.ConcurrentHashMap原理

    并发下的HashMap,HashMap是线程不安全的,虽然HashTable可以代替HashMap,达到线程安全的目的,但是HashTable是用一个全局锁来同步不同的线程,这样性能就大大降低了,而ConcurrentHashMap用volatile修饰变量保证了安全高效的读操作,而利用锁分段技术保证了高并发情况下的写操作。

9.X 深拷贝和浅拷贝

    深拷贝和浅拷贝就是指对象的拷贝,一个对象存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用

  • 浅拷贝:只会拷贝基本数据类型的值,以及实例对象的引用地址。拷贝的对象和内部的属性指向的对象是同一个
  • 深拷贝:拷贝基本数据类型的值,也会对实例对象引用地址所指向的对象进行复制,深拷贝出来的对象和内部的属性指向的不是同一个对象

10.如何实现一个IOC容器

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

 11.什么是字节码?采用字节码的好处是什么?

         java中的编译器和解释器:

         java中引入了虚拟机的概念,即在编译程序和机器之间加入了一层抽象的虚拟的机器(JVM)。这台机器在任何平台上都提供给编译程序一个共同的接口

         编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在java中,这种供虚拟机理解的代码叫做字节码(即扩展名为.class的文件),他不面向任何特定的处理器,只面向虚拟机

         java源代码——>编译器——>jvm可执行的java字节码——>jvm——>jvm中的解释器——>机器可执行的二进制机器码——>程序运行

         采用字节码的好处:

         java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点(跨平台)。

12. java类加载器

       JDK自带有三个类加载器:

  • BootStrapClassLoader:是ExtClassLoader的父加载器,默认负责加载%JAVA_HOME%lib下的jar包和class文件
  • ExtClassLoader是AppClassLoader的父类加载器,负责加载%JAVA_HOME% /lib/ext文件夹下的jar包和class类
  • AppClassLoader是自定义类加载器的父类,负责加载classpath下的文件。

      继承ClassLoader可以实现自定义类加载器

13. 双亲委派机制

                              

 

  • 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把请求委托给父类的加载器去执行
  • 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终到达顶层的启动类加载器(BootStrapClassLoader)
  • 如果父类加载器可以完成类加载任务,就返回成功,如果不能,就会向下尝试让子加载器完成加载任务

 向上委派到顶层加载器为止,向下查找到发起加载的加载器为止

 双亲委派的好处:

  • 主要是为了安全,避免用户自己编写的类动态替换java的一些核心类
  • 同时也避免了类的重复加载,因为一旦加载完成就会返回成功。

13.X. 沙箱安全机制

    比如说自定义String的类,但是在在加载自定义String的类的时候会率先使用引导类加载器加载,而引导类加载器在加载过程中先加载jdk自带的文件(rt.jar包中java\lang\String.class),报错信息说没有main方法,就是因为加载的是rt.jar包中的String类。这样可以保证对java核心源代码的保护

14. java中的异常体系

     java中的所有异常都来自顶级父类Throwable

     Throwable下有两个子类Exception和Error

     Error是程序无法处理的错误,一旦出现这个错误,则程序将被迫停止运行

     Exception不会导致程序停止,RunTimeException发生在程序运行过程中,会导致当前线程执行失败

14.k JMM

     不同线程拥有各自的私有工作内存,当线程需要读取或修改某个变量时,不能直接去操作主内存中的变量,而是需要将这个变量复制到线程的工作内存中,当该线程值后,其它线程并不能立刻读取到新值,需要将修改后的值刷新到主内存中,其它线程才能从主内存读取到修改后的值,这是因为加了volatile关键字保证了可见性。但是不保证原子性

14.t CAS

          CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,就重写获取内存值和预期值比较,一直这样循环直到操作完成(自旋思想)。有效的解决了原子性问题

    缺点:

  • 循环时间长,消耗大
  • 只能保证一个共享的原子操作
  • 引出ABA问题

14.l.  ABA问题

       CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差内会导致数据的变化。比如说一个线程one从内存位置V复制A到自己的运行内存,这时候另一个线程two也从内存复制A到自己 的运行内存,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成了A,这时候one进行CAS操作发现内存中仍然是A。然后线程

14.o 公平锁和非公平锁

     ReentrantLock默认是非公平锁,可以通过构造函数(改为true)改变为公平锁。Synchronized是一种非公平锁。非公平锁的优点在于吞吐量比公平锁大。

     自旋锁

     指尝试获取锁的线程不会立马阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

      读锁可以多个线程共存,写锁只能一个锁

14.f synchronize和lock有什么区别,用新的lock有什么好处

  • Synchronized是java内置的关键字,Lock是一个java类
  • Synchronizedh会自动释放锁,Lock必须要手动释放锁!否则会死锁
  • 两者都是非公平锁,Lock可以通过传一个参数true将锁改为非公平锁
  • Synchronized适合少量的代码同步问题,Lock适合大量的同步代码

14.w 锁升级,三种锁的优缺点

                         

14.X. 强引用、弱引用、软引用、需引用

从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

  • 强引用:我们使用的大部分引用都是强引用,比如new一个list集合,new一个数组。如果一个对象具有强引用,那么垃圾回收器绝不会回收它。当内存空间不足时,java虚拟机宁愿抛出OutOfMemoryError(内存溢出)错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
  • 软引用:如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
  • 弱引用:弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
  • 虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。

14.p 对垃圾回收器的理解

       GC算法(计数、复制、标记清除、标记整理)是内存回收的方法论,垃圾收集器就是算法落地实现

    四种常用垃圾回收器:

  • 串行垃圾回收器(Serial):它为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程,所以不适合服务器环境
  • 并行垃圾回收器(Parallel):多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算等弱交互场景
  • 并发垃圾回收器(CMS):用户线程和垃圾收集线程同时执行(不一定并行,可能交替执行),不需要停顿用户线程,适用于对响应时间有要求的场景
  • G1垃圾回收器:G1不区分新生区和老年区而是把内存划分成多个子区域(Region),能充分利用CPU、多核环境硬件优势,尽量缩短了STW。

      

14.x 单例模式

     1.懒汉模式:懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。

                                                                

 

    2.饿汉模式:饿汉式,从名字上也很好理解,就是“比较勤”,实例在初始化的时候就已经建好了,不管你有没有用到,都先建好了再说。好处是没有线程安全的问题,坏处是浪费内存空间。

                                                               

 

    3. 双检锁:双检锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。

                                                              

 

   4. 静态内部类:静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

                                                               

 

    5. 枚举:枚举的方式是比较少见的一种实现方式,但是看下面的代码实现,却更简洁清晰。并且她还自动支持序列化机制,绝对防止多次实例化。

                                                                

15. GC(Garbage Collection 垃圾回收机制)如何判断对象可以被回收

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

GC Roots的对象有:

  • 虚拟机栈中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中引用的对象

15.k 垃圾回收的常见算法

       1. 复制算法

         新的对象实例被创建的时候通常在Eden空间,发生在Eden空间上的GC称为Minor GC,当在新生代发生一次GC后,会将Eden和其中一个Survivor空间的内存复制到另外一个Survivor中,如果反复几次有对象一直存活(年龄16),此时内存对象将会被移至老年代。缺点浪费空间

        2.标记清除

         分为标记和清除两个阶段,先标记出要清除的对象,然后统一回收这些对象,缺点内存碎片化,导致装不下比较大的对象

        3.标记整理算法

         在标记清除算法上的优化,标记-压缩算法首先还是“标记”,标记过后,将不用回收的内存对象压缩到内存一端,此时即可直接清除边界处的内存,这样就能避免复制算法带来的效率问题,同时也能避免内存碎片化的问题

15.X 说一说JVM中,哪些是共享区,哪些是gc root

          1.堆区和方法区是共享的,栈、本地方法栈和程序计数器是每个线程独有的

         

 

           gc root :栈中的本地变量,方法区中的静态变量、本地方法栈中的变量、正在运行的线程等都可以作为gc root 

 

16.k、进程与线程
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

16.l start()和run()的区别

  • 用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体中的代码执行完毕而直接继续执行后续的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法
  • run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

总结:调用start方法方可启动线程,而run方法只是thread类中的一个普通方法调用,还是在主线程里执行。

16.z 线程池中execute和submit的区别

 execute和submit都是开启任务池中的任务

  • 接收的参数不一样,excute参数是new runable(),submit是new runable或者new callable
  • submit有返回值,而execute没有
  • submit方便Exception处理

16.线程的生命周期,线程有哪些状态

1.线程通常有五种状态:创建、就绪、运行、阻塞和死亡状态。

2.阻塞情况有三种:

  • 等待阻塞:运行的线程执行wait方法,该线程会释放占用的资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
  • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
  • 其他阻塞:运行的线程执行sleep或者join方法,或者发出了I/O请求时,JVM会把该线程设置为阻塞状态。当sleep状态超时时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法。

17.sleep()、wait()、join()、yield()的区别

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

sleep和wait的区别:

  • sleep是Thread类的静态本地方法,wait是Object的本地方法
  • sleep方法不会释放lock,但是wait会释放,而且会加入到等待队列中(sleep是将CPU的执行权和执行资格释放出去,等定时时间结束再取回CPU资源)
  • sleep方法不依赖与同步器synchronized,但是wait需要依赖synchronized关键字
  • sleep不需要被唤醒(休眠之后退出阻塞),但是wait需要唤醒
  • sleep一般用于当前线程休眠,或者轮循暂停操作,wait则多用于多线程之间的通信
  • sleep会让出CPU执行时间且强制上下文切换,而wait不一定,wait后可能还是有机会重新竞争到锁继续执行的

yield()执行后线程直接进入就绪状态,马上释放了CPU的执行权,但是依然保留了CPU的执行资格,所以有可能CPU下次进行线程调度还会让这个线程获取到执行权继续执行。

join()执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那么线程B就会进入到阻塞队列,直到线程A结束或中断线程

18.说一说对线程安全的理解

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

19.说说你对守护线程的理解

        为用户线程服务,比如:GC垃圾回收线程

20.阻塞队列

       当阻塞队列是空时,从队列中获取元素的操作会被阻塞。

       当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。

       处于队列的线程不用程序猿管理wait和notify,全部都阻塞队列管理

21.并发、并行、串行的区别

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

22.并发的三大特性

1.原子性:原子性是指一个操作中CPU不可以在中途暂停然后调度,即不被中断操作,要么全部执行完成,要么都不执行。就好比转账,从账户A向账户B转账1000元,执行成功必然有两个操作:A账户减1000,B账户加1000。

2.可见性:当多个线程访问一个变量时,一个线程修改了这个变量的值,其他线程能够立刻看到修改的值。

                 若两个线程在不同的CPU,那么线程1改变了 i 的值,并且刷新到主存,线程2又使用了 i ,那么 i 是线程1操作后的值,这就保证了可见性。

3.有序性:虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重新排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现安全问题。

volatile保证了可见性和有序性,但是没办法保证原子性,保证原子性需要加锁。

23.为什么使用线程池?解释下线程池?

 线程池底层主要是靠ThreadPoolExecutor实现的,它包含七个参数。

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

线程池参数:

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

     四种拒绝策略(Handler):AbortPolicy(默认)直接抛出异常;CallerRunsPolicy将处理不了的任务返回给调用者;DiscadOldestPolicy抛弃队列中等待最久的任务;DiscardPolicy直接丢弃任务。

24.线程池处理流程

                             

 

24.j 死锁

 死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那他们都将无法推进下去

24.X shutdown和ShutdownNow的区别

  • shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。
  • 而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。

 

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

1. 一般的队列只能保证作为有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务

    阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使得线程进入wait状态,释放CPU资源

    阻塞队列自带阻塞和唤醒功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占用CPU资源。

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

26.线程池中,线程复用原理

     线程池将线程和任务进行解耦,线程是线程,任务是任务。摆脱了之前通过Thread创建线程时的一个线程必须对应一个任务的限制。

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

27.BeanFactory和ApplicationContext有什么区别

     ApplicationContext是BeanFactory的子接口

     BeanFactory是spring里面最底层的接口,包含了各种Bean的定义,读取Bean的配置文件,管理bean的加载、实例化、控制bean的生命周期,维护bean之间的依赖关系

     ApplicationContext提供了更完整的功能:

  1. 继承MessageSource,因此支持国际化
  2. 统一的资源文件访问方式
  3. 在监听器中注册bean的事件
  4. 同时加载多个配置文件
  5. 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层
  • BeanFactory采用的是延迟加载形式来注入Bean的,只有在使用到某个bean是,在对该bean进行实例化
  • ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。
  • 相对于基本的BeanFactory,ApplicationContext唯一的不足是占用内存空间。当应用程序配置bean较多时,程序启动较慢。
  • BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader
  • BeanFactory和ApplicationContext都支持BeanProcessor。BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册

28. 描述一下Spring Bean的生命周期

 

29 Spring Boot 、Spring MVC 、Spring 有什么区别

  • spring 是一个IOC容器,用来管理bean,使用依赖注入实现控制反转,可以很方便的整合各种框架,提供AOP机制
  • spring mvc是spring对web框架的一个解决方案,提供了一个总的前端控制器Servlet,用来接收请求,然后定义了一套路由策略及适配执行Handler,将Handler结果使用视图解析技术生成视图展现给前端
  • spring boot是spring提供的一个快速开发工具包,让程序员能更方便。快速的开发spring + springMVC应用。简化了配置(约定大于配置),整合了一系列的解决方案(starter机制)、redis、mongodb、es,可以开箱即用。

30.springMVC工作流程

         

 31.mybatis的优缺点

优点:

  • 基于SQL语句编程,相当灵活,不会对应用程序或数据库现有设计造成任何影响,SQL写在XML里,解除与程序代码耦合,便于统一管理,提供xml标签,支持动态编写SQL语句。
  • 与jdbc相比减少了大量代码,不需要手动打开和关闭连接
  • 提供映射标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件维护

缺点:

  • SQL语句的编写工作量大,尤其当字段多,关联表多的时候
  • SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

32.#{}和${}的区别

  • #{}是预编译处理、是占位符,${}是字符串替换,是拼接符
  • Mybatis在处理#{}时,会将SQL中的#{}替换为?号,调用PrepareStatement来赋值
  • Mybatis在处理${}时,就是把${}替换成变量的值,调用Statement来赋值
  • #{}的变量替换是在DBMS中、变量替换后,#{}对应的变量自动加上单引号
  • ${}的变量替换是在DBMS之外、变量替换后,${}对应的变量不会加上单引号
  • 使用#{}可以有效的防止SQL注入,提高系统安全性

33.索引的基本原理

索引是快速地寻找具有特定值的记录。如果没有索引,一般来说执行查询时需要查询整张表

索引的原理,把无序的数据变成有序的查询

  1. 把创建了索引的列的内容进行排序
  2. 对排序结果生成倒排表
  3. 在倒排表内容上拼上数据地址链
  4. 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到数据

33.t、  mysql索引

  • 主键索引:唯一标识,主键不可重复,只能有一个列作为主键
  • 唯一索引:避免重复的列出现,可以重复,多个列都可以标识为唯一索引
  • 常规索引:默认的,index、key关键字来设置
  • 全文索引:在特定的数据库引擎下才有,MyISAM,快速定位数据

34.mysql聚簇和非聚簇索引的区别

都是B+树的数据结构

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

聚簇索引的优势:

  1. 查询通过聚簇索引可以直接获取数据,相比非聚簇索引需要第二次查询的效率要高
  2. 聚簇索引对于范围查询的效率很高,因为其数据是按照大小排列的
  3. 聚簇索引适合用在排序的场合,非聚簇索引不适合

35.mysql索引的数据结构,各自优势

      较多的有Hash索引,B+树索引。大多数需求时单条记录查询的时候选择Hash索引,其他大部分场景选择BTree索引

36.事务的基本特性和隔离级别

  • 原子性:一个事务中的操作要么全部成功,要么全部失败
  • 一致性:数据库总是从一个一致性的状态转换到另一个一致性的状态
  • 隔离性:一个事务的修改在最终提交前,对其他事务是不可见的
  • 持久性:一旦事务提交,所做的修改就会永久的保持到数据库中

隔离性有四个隔离级别:

  • read uncommit 读未提交:可能读到其他事务未提交的数据,也叫做脏读。用户本来应该读取到id=1的用户,age应该是10,结构读取到了其他还没有提交的事务,结构读取结果为age=20,这就是脏读
  • read commit 读已提交:两次读取结果不一致,叫做不可重复读。不可重复读解决了脏读的问题,他只会读取已提交的事务。用户开启事务读取id=1的用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读
  • repeatable read 可重复读:这是mysql的默认级别,就是每次读取的结果都一样,但是有可能产生幻读(事务A 按照一定条件进行数据读取, 期间事务B 插入了相同搜索条件的新数据,事务A再次按照原先条件进行读取时,发现了事务B 新插入的数据 称为幻读)
  • serializable 串行:一般不会使用,他会给每一行读取的数据加锁,会导致大量超时的锁和锁竞争的问题

37.简述MyISAM和InnoDB的区别

读写分离时,MyISAM一般用作读库的引擎,InnoDB一般用作写库的存储引擎

MyISAM:

  • 不支持事务,但是每次查询都是原子性的
  • 支持表级锁,即每次操作是对整个表加锁
  • 存储了表的总行数
  • 一个MyISAM表有三个文件:索引文件、表结构文件、数据文件
  • 采用非聚簇索引

InnoDB:

  • 支持ACID的事务,支持事务的四种隔离级别
  • 支持行级锁及外键约束;因此可以支持写并发
  • 不存储表的总行数
  • 采用聚簇索引

38.RDB和AOF机制

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

     优点:

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

     缺点:

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

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

        优点:

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

        缺点:

  • AOF文件比RDB文件大,且恢复速度慢
  • 数据集较大时,比rdb启动效率低
  • 运行效率没有RDB高

AOF文件比RDB更新频率高,优先使用AOF还原数据

39. 缓存雪崩、缓存穿透、缓存击穿

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

      解决方案:

  • 缓存数据的过期时间设置为随机,防止同一时间大量数据过期现象发生。
  • 给每一个缓存数据增加相应的缓存标记,记录缓存是否失效,如果缓存标记失效,则更新数据缓存(这样消耗性能)
  • 缓存预热(提前将数据添加到缓存中)
  • 互斥锁

      缓存穿透:指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉(一般是恶意攻击造成)

      解决方案:

  1. 报警
  2. 接口层增加校验,如用户鉴权校验,id<=0直接拦截
  3. 缓存和数据库都没有的值,将key-value对写为key-null,有效时间可以设置短点,这样可以避免同一个id反复恶意攻击
  4. 采用布隆过滤,将所有可能的值存到一个足够大的bitmap中,一个一定不存在的数据会被拦截掉。

       缓存击穿:指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,与雪崩不同,缓存击穿是指查同一条数据。

       解决方案:

  •  设置热点数据永不过时
  • 加互斥锁

 40. Redis主从复制的核心原理

       通过执行slaveof命令或设置slaveof选项,让一个服务器去复制另一个服务器的数据。主数据库可以进行读写操作。当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

       全量复制:

  • 主节点通过bgsave命令fork子进程RDB持久化,该进程是非常消耗CPU、内存(页表复制)和硬盘IO
  • 主节点通过网络将RDB文件发送给从节点,对主从节点的宽带都会带来很大的消耗
  • 从节点清空老数据,载入新RDB文件的过程是阻塞的,无法响应客户端的命令。

        部分复制:

  • 复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset
  • 复制积压缓冲区:主节点内部维护了一个固定长度的、先进先出(FIFO)队列 作为复制积压缓冲区,当主从节点offset的差距过大超过缓冲区长度是,将无法执行部分复制,只能执行全量复制
  • 服务器运行ID(runid):每个Redis节点,都有其运行ID,运行ID由节点在启动时自动生成,主节点会将自己的运行ID发送给从节点,从节点会将主节点的运行ID存起来。从节点Redis断开重连的时候,就是根据运行ID来判断同步进度

41.CAP理论,BASE理论

     Consistency(一致性):

  • 即更新操作成功并返回客户端后,所有节点在同一时间的数据完全一致。
  • 对于客户端来说,一致性指的是并发访问时更新过的数据如何获取的问题
  • 从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致

     Availability(可用性):

  • 即服务一直可用,而且是正常响应时间。系统能够很好的为用户服务,不出现操作失败或者访问超时等用户体验不好的情况

     Partition Tolerance(分区容错性):

  •  即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或几个机器宕掉了,其他剩下的机器还能够正常运转满足系统要求,对于用户而言并没有什么体验上的影响。

      CP和AP:分区容错是必须保证的,当发生网络分区时,如果要继续服务,那么强一致性和可用性只能二选一

      BASE是Basically Available(基本可用)、Soft state(软状态)、Eventually consistent(最终一致性)

      BASE理论是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的总结,是基于CAP定论逐步演化而来的。BASE理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点采用适当的方式来使系统达到最终一致性。

       Basically Available(基本可用):

  • 响应时间上的损失:正常情况下,处理用户请求需要0.5s返回结果,但是由于系统出现故障,处理用户请求的时间变为3s。
  • 系统功能上的损失:正常情况下,用户可以使用系统的全部功能,但是由于系统访问量突然剧增,系统的部分非核心功能无法使用

       Soft state(软状态):

  • 数据同步允许一定的延迟

      Eventually consistent(最终一致性):

  • 系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态,不要求实时

42. 负载均衡算法、类型

  • 轮询法:将请求顺序轮流的分配到后端服务器上
  • 随机法:通过随机算法,随机选取其中一台服务器进行访问
  • 源地址哈希法:根据获取客户端的IP地址,通过哈希函数计算得到一个值,用该数组对服务器列表的大小进行取模运算,得到的结果便是客户端要访问服务器的序号
  • 加权轮询法:还是请求顺序轮流的分配到后端服务器上,给配置高、负载低的机器配置更高的权重。
  • 加权随机法:还是请求随机分配到后端服务器上,给配置高、负载低的机器配置更高的权重。
  • 最小连接数法:动态地选取连接数最少的一台服务器。

      类型:Nginx、HAproxy、LVs

 

posted on 2021-06-22 20:07  Bjtino  阅读(133)  评论(0)    收藏  举报