Java面试知识点笔记

1. 基础

  • 面向对象的特征            

封装:将客观事物封装成抽象的类,并对属性和方法实现访问控制;

继承:继承父类的非私有属性和方法,分为实现继承和接口继承,实现继承不需要实现方法,接口继承继承抽象方法,需要实现方法;

多态:一个实例的相同方法在不同的情形下有不同的表现形式,父类引用不同的子类实现;

抽象:数据抽象->类的属性,过程抽象->类的方法。

  • final, finally, finalize 的区别        

final:常量关键字,标识属性时,属性在赋值后不可被改变,标识方法后,方法不可被重写,但可被重载,标识类时,类不可被继承;

finally:用try catch后面,表示,不管有无异常发生,finally里的代码最后都会被执行,一般用来进行一些连接的关闭;

finalize:Objct中的一个方法,任何类都可以重写它,该方法在垃圾回收器清除对象之前被调用。

  • int和Integer有什么区别

  1. int属于基本数据类型,默认初始值为0;Integer属于对象类型,默认初始值为null。
  2. int和Integer之间可以自动转换,叫做装箱和拆箱,如Integer i=0 属于自动装箱,int i=new Integer(127) 属于自动拆箱。
  3. 注意,int的自动装箱在范围-128~127之间时会进行缓存,Integ都是同一个对象,而超过这个范围就是不同的对象,
  4. 所以Integer a =128;Integer b =128; Sysytem.out.print(a == b)输出值为false。
  • intValue、parseInt和valueof的区别

  1. intValue把Interge对象转换成int
  2. parseInt是把String对象转换成int
  3. valueof把String对象转换成Integer对象
  • ==和equals的区别

  1. ==比较对象的地址,即两个对象是不是同一个对象
  2. eauals比较对象的内容是不是一样,一般用来比较两个字符串是不是相同
  3. 注意:String a= "abc"; String b = "abc"; String c = new String("abc"); 第一种方式创建一个字符串常量,放在常量池中,同一个常量在常量池中只有一个,即a==b为true,而new String("abc")方式则是在堆中创建一个对象,所以a==c为false。

当然,equals可以被重写,但必须遵循以下五个原则:

  1. 自反性:x.equals(x)为true
  2. 传递性:a.equals(b)为true,b.equals(c)为true,则a.equals(c)也必须为true
  3. 对称性:a.equals(b)为true,b.equals(a)也要为true
  4. 一致性:对象不变,比较的结果也不变
  5. 非空型:x.equals(null)为false
  • 三目运算符空指针问题

     1. 三目运算符会对统一冒号两边的数据类型,可能引起空指针,如下
 

Long aLong = null;
Long bLong = null;
Long s = aLong == null ? bLong : 0;//会报空指针,bLong自动拆箱
Long s1 = aLong == null ? bLong : aLong;//不会发生自动拆箱,不会报空指针
Long s2 = aLong == null ? 0 : aLong;//不会发生自动拆箱,不会报空指针

Long s3 = aLong == null ? bLong  : aLong + bLong;//会报空指针,相加封装类型会自动拆箱
  • 重载和重写的区别

  1. 重载:和原方法相比,参数类型、个数或顺序不同,返回值可以相同也可以不同,重载是一个类中一种多态性的表现
  2. 重写:只改变方法的具体实现,不改变参数和返回值,发生在子类继承或实现父类的情况下。
  • 抽象类和接口有什么区别

  1. 抽象类允许有非抽象方法,也可以没有,可以有普通属性,不可以实例化;
  2. 接口所有方法都是抽象方法(jdk1.8 可以有默认实现),只可以有公共静态常量,没有构造器,不能实例化,接口可继承接口,并且可以多继承。
  • 说说反射的用途及实现

  1. 反射,是指在运行时能获取类的属性、方法、接口和父类等信息。通过newInstance()方法生成类实例,获取私有变量,反射可以无视类的访问控制;
  2. 反射一般和注解一起用,可以在运行时为方法配置一些外部属性。比如可以同过注解在运行时反射实体类属性对应的数据库表字段。
  • 说说自定义注解的场景及实现

  1. 通过@interface自定义注解,自定义注解类上面还可以加元注解,标志注解使用周期和使用范围,包括类、属性、方法、注解。
  2. 使用场景:通过自定义注解,定义对象对应数据表的属性,如表名,表字段等。
  • HTTP 请求的 GET 与 POST 方式的区别

GET:从服务器请求数据,不应该进行修改,参数显示在url后面,可缓存

POST:向服务器提交数据,进行数据的更新,参数保存在HTTP请求包的包体中,不可缓存。

    1. get请求会被浏览器主动缓存,而post不会
    2. get请求回退是无害的,而post会再次提交请求
    3. get请求只能进行url编码,而post支持多种编码方式
    4. get产生一个tcp数据包,浏览器会把http header 和 data一并发出去,服务器响应200,返回数据;post产生两个tcp数据包,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200
    5. get不能用来传递敏感信息
  • session 与 cookie 区别

session:由于http是无状态协议,所以用session来保存用户的会话状态,session保存在服务器,用户登录后服务器会返回给客户端一个名为JsessionId的cookie,用户再次访问时就会带上这个cookie,服务器通过它找到相应的session,session cookie针对某一次会话而言,会话结束session cookie也就消失了,session是需要借助cookie才能正常工作的,如果客户端完全禁止cookie,session将失效;

cookies:cookies保存在客户端浏览器中,通过session和cookie可以实现自动登录。

  • session 分布式处理

实现session分布式处理,主要实现各模块通过JSESSIONID去拿到session,session可以缓存到redis中。

session分布式处理的主要问题:

  1.    session过期。 通过Timing Wheel(时间轮),管理大量定时任务,定时销毁过期session。

  2.    登出。利用消息中间件来通知处理logout,如RabbitMq。

分布式集群session的几种处理方式:

  • 粘性session

粘性Session是指将用户锁定到某一个服务器上,同一个用户访问的都是同一台服务器。

优点:简单,不用对session做任何处理。

缺点:缺乏容错性,如果当前访问的服务器发生故障,用户被转移到第二个服务器上时,他的session信息都将失效。

  • 服务器session赋值

任何服务器上的session发生改变,该节点会把这个session的所有内容序列化,然后给其他节点保存,以此保证session同步。

优点:可容错,各服务器间session能够实时响应。

缺点:网络负荷压力大,节点多或访问量大时性能低下。

  • session共享机制(主要使用)

使用分布式缓存方案,如redis集群保存session。

  • session持久化到数据库

优点:服务器出现问题,session不会丢失。

缺点:如果网站的访问量很大,把session存储到数据库中,会对数据库造成很大压力,读取session较慢。

  • terracotta实现session复制

Terracotta的基本原理是对于集群间共享的数据,当在一个节点发生变化的时候,Terracotta只把变化的部分发送给Terracotta服务器,然后由服务器把它转发给真正需要这个数据的节点。可以看成是对第二种方案的优化。

优点:这样对网络的压力就非常小,各个节点也不必浪费CPU时间和内存进行大量的序列化操作。把这种集群间数据共享的机制应用在session同步上,既避免了对数据库的依赖,又能达到负载均衡和灾难恢复的效果。

       详细讲述分布式session传送门

  • java io中的阻塞非阻塞与同步异步

  • io分为内存io、网络io和磁盘io,我们说的io通常指的是后两种。通常用户进程的一个完整io分为两个阶段:
  1. 用户进程空间<——>内核空间

  2. 内核空间<——>设备空间(磁盘网络等)

网络io通常包括下面两个阶段:

  1. 等待网络数据到达网卡—>读取到内核缓冲区,数据准备好

  2. 从内核缓冲区复制数据到进程空间;

  • 同步与异步关注的是消息通信机制
  1. 所谓同步,就是在发出一个“调用”时,在没有得到结果之前,该调用就不会返回,但是一旦调用返回,就得到返回值了;

  2. 异步则是相反,“调用”在发出之后,调用者不会立即得到结果,而是在“调用”发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

  • 阻塞与非阻塞关注的是程序在等待调用结果时的状态
  1. 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回

  2. 非阻塞调用是指在不能立即得到结果之前,该调用不会阻塞当前线程。

  • 同步io和异步io:
  1. 同步io:当用户发出io请求操作之后,如果数据没有准备就绪,需要通过用户线程或内核不断去轮询数据是否就绪,当数据就绪时,再将数据从内核拷贝到用户线程;

  2. 异步io:只有io请求操作的发出是由用户线程来进行的,内核自动完成数据是否准备就绪和数据拷贝到用户空间的过程,然后通知用户线程io操作已经完成;

  • 阻塞io和非阻塞io:
  1. 阻塞io:内核在检查数据未就绪时,会一直等待,直到数据就绪;

  2. 非阻塞io:如果数据没有就绪,则会返回一个标志消息告知用户线程当前要读的数据没有准备就绪;
     

  • 什么是值传递和引用传递

  1. 值传递是对基本类型变量而言,传递的是变量的一个副本,改变副本不影响原变量;
  2. 引用传递一般是对对象型变量而言,传递的是该对象的地址,改变该地址的对象会影响原对象;
  • 枚举

  1. 枚举的构造方法只能是私有的
  2. 枚举有几个实例,调用时就会执行多少次构造方法
  • 各数据结构操作的时间复杂度

Java面试知识点笔记_第1张图片

 

 

2. 集合

  • list 、set、map

  • list:collection接口的子类,是一个有序容器,可以插入重复元素,可以根据索引直接操作元素,常用的实现类有ArrayList、LinkedList和Vector   
  1. ArrayList:底层由数组实现,增删慢,查找快,线程不安全

  2. LinkedList:底层由链表实现,增删快,查找慢,线程不安全,LinedList比ArrayList更占内存

  3. Vector:底层由数组实现,增删慢,查找快,线程安全              

  • set:collection接口的子类,是一个无序容器,不保存重复元素,不能根据索引直接操作元素,常用的实现类有HashSet、TreeSet和LinkedHashSet
  1. HashSet:底层数据结构是哈希表(即实现了一个HashMap),依赖于hashCode()和equals两个方法

  2. TreeSet: 底层数据结构是红黑树(一个自平衡的二叉树,TreeMap),TreeSet保证保证元素的排序方式,自然排序(元素实现Comparable接口)和比较器排序(元素实现Comparator接口)

  3. LinkedHashSet:底层数据结构为链表和哈希表,由链表保证元素有序,由哈希表保证元素唯一           

  • map:不继承其他接口,map是一个键值对容器,键不能重复,值可以重复,常用的实现类有HashMap、Hashtable、LinkedHashMap、TreeMap和ConcurrentHashMa  
  1. HashMap:基于散列表实现,插入和查询键值对的开销固定,线程不同步,hashMap的键和值可以接受null

  2. Hashtable:与HashMap类似,但是是线程同步的

  3. LinkedHashMap:通过链表保存了记录的插入顺序,用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,线程不同步    

  4. ConcurrentHashMap:实现分段加锁,兼顾了线程安全性和运行的效率                    

  • 集合与数组的区别

  1. 数组固定,集合可变(集合达到设定的最大容量后会扩容);
  2. 数组可以存储基本类型,也可以存储引用类型,集合只能存储引用类型;
  3. 数组只能存储同一类型,集合可以存储不同类型;       
  •   HashMap和ConcurrentHashMap的区别

  1. HashMap实际上是一个“链表散列”的数据结构,是数组和链表的结合体,HashMap底层就是一个数组,数组的元素是一个链表Entry;

    当要插入一个键值对时,首先计算key的hash值,利用key的哈希值与map的容量进行二进制与运算,获得键值对的在数组中的插入位置,若发生冲突,即两个键值对插在同一个位置,新的键值对会放在链表的头部,hashMap认为后插入的Entry被查找的可能性更大,这里,Entry是一个双向循环链表的时,查找可能会发生死循环;

    HashMap有两个重要的参数容量和负载因子,随着插入的值越来越多,hashmap发生冲突的数量就越多,这时候,hashmap就需要扩容,hashmap在当size大于初始容量时,会进行扩容,并且每次自动扩展或手动初始化时,长度必须是2的幂次方,这是为了让键值对在hashmap中更均匀的分布,因为2的幂次方-1后的二进制值都为1,在做与运算时,键的hash值能更好的散列,扩容时会对原来的key重新进行散列,使其均匀地分布在新的hash表中

  2. 多线程在操作HashMap时会出现环形链表进而导致死循环的问题,为了解决这个问题,同时兼顾运行效率,采用ConcurrentHashMap来代替HashMap;

    ConcurrentHashMap通过使用Segment的方式来减少悲观锁的产生,ConcurrentHashMap可以看成一个二级哈希表,第一个哈希表中存储Segment对象,而Segment对象里又维护了一个哈希表,所以ConcurrentHashMap在进行get、put操作是要对key进行俩次哈希计算,第一次哈希计算找到Segment,第二次哈希计算找到真正键值对存放的位置;

    ConcurrentHashMap只在put操作时对Segment加锁,因此,不同线程在操作不同Segment时不会阻塞,减少了悲观锁的产生,提高了效率;

    ConcurrentHashMap是使用了类似于CAS乐观锁的思想来保证size方法统计时不会出现问题,用modCount变量就是用来统计当前集合修改次数;

                

  • 迭代器的快速失败和安全失败

  1. 快速失败(fail-fast):在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增添、删除、修改),则会抛出ConcurrentModificationException异常,java.util包下的集合类都是快速失败的,不能再多线程下发生并发修改(或迭代过程中被修改)。
  2. 安全失败(fail-safe):采用安全失败机制的容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历,不会触发ConcurrentModificationException异常。java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。

  

 

3. 线程

  • 创建线程的方式及实现

  1. 继承Thread类,重写run方法,不能再继承其他类,资源不共享;
  2. 实现Runnable接口,重写run方法,可以继承其他类,资源共享;
  3. 使用Callabe和Future创建线程,重写call方法,有返回值,资源共享;
  • sleep(),wait(),yield()和join()方法的区别

  1. sleep()方法让当前线程等待指定时间,暂时进入阻塞状态,停止执行,指定时间过后恢复执行,不释放锁;
  2. wait()和notify()、notifyAll()都是Object方法,必须在synchronized语句块内使用,也就是说调用这些方法前必须拥有对象的锁,wait()方法释放锁,暂停当前线程,并将当前线程放入等待队列中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,让等待线程竞争锁;
  3. yield()方法和sleep()方法类似,也不会释放锁,yield()方法只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行;
  4. join()方法会使当前线程等待调用join()方法的线程结束后才能继续执。
  • 说说 CountDownLatch 、CyclicBarrier 和 Semaphore 区别

  1. countDownLatch是个同步工具类,可以指定一个计数值,在并发环境下线程进行减一,当计数为零时,会唤醒被awit方法阻塞的线程,实现线程间的同步。与join()方法的区别是join需要等待线程执行完毕,而countDownLatch只需要线程进行countDown就行;countDownLatch计数到0时,计数无法被重置;        
  2. CyclicBarrier是加计数方式,计数达到构造方法中参数指定的值时释放所有等待的线程。一组线程使用await()指定barrier,所有线程都到达各自的barrier后,再同时执行各自barrier下面的代码;CyclicBarrier计数达到指定值后,计数置为0,重新开始;
  3. semaphore信号量,用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源
  • ThreadLocal 、Volatile、Synchronized、Lock 比较

  1. ThreadLocal:线程本地变量,ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,但内存的占用会比不使用ThreadLocal要大;
  2. volatile用来在多线程中同步变量,当一个变量被volatile修饰后,该变量就不能被缓存到线程的内存中,保证了变量的可见性(即确保所有线程都能读到准确的值),但不保证原子性;
  3. synchronized关键字是Java利用锁的机制自动实现的,一般有同步方法和同步代码块两种使用方式,保证同步代码块同一时间只有一个线程执行;
  4. lock是显示加锁、synchronized是隐式加锁,lock是乐观锁机制,synchronized是悲观锁机制,乐观锁是指默认锁是空闲的,每次都会直接获取;悲观锁是当锁被占用的时候,其他线程只能处于阻塞状态,直到锁被释放
  • 线程的生命周期

线程有以下五种状态:

  1. new:新建状态,新创建了一个线程对象;
  2. runnable:就绪状态,当线程对象调用了start()方法之后,该线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,表示方法可以运行了,至于该线程何时开始运行,取决于JVM里线程调度器的调度,当线程时间片用完了,调用当前线程的yield()方法,线程也会进入就绪状态;
  3. running:运行状态,就绪状态的线程获得cpu时间片,执行程序代码;
  4. blocked:阻塞状态,阻塞状态是指线程因为某种原因放弃了cpu 使用权,暂停运行,进入等待队列,变成阻塞状态,阻塞的情况分三种:

           等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列中;
           同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中;
            其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
  5. dead:死亡状态,线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

线程的状态转换图:

          

Java面试知识点笔记_第2张图片

Java面试知识点笔记_第3张图片

 

 

 

  • 线程池的实现原理

  1. 线程池时为了重复利用已执行完了线程,避免因大量线程的销毁和创建而降低系统的效率;
  2. 线程池的四个重要参数:
         a. corePoolSize:核心线程数
         b. maximumPoolSize:最大线程数
         c. keepAliveTime:空闲线程存活时间
         d. workQueue:缓存队列,用来存储等待执行的任务
  3. 创建线程:
        a. 当任务数量不超过核心线程数时,线程池都会为该任务创建新的线程;
        b. 当任务数量不超过核心线程数时,线程池都会为该任务创建新的线程;当任务数量超过核心线程数但不超过缓存队列大小时,线程池不会创建新的线程;
        c. 当任务数量超过核心线程数 + 缓存队列大小但不超过最大线程数,且核心线程无空闲,创建新的线程(最多为最大线程数);
        d. 当任务数量超过最大线程数,且无线程空闲时,抛出拒绝任务异常。

4. JVM

  • 类的实例化顺序

       父类 > 子类,静态 > 非静态 > 构造方法

  1. 父类的静态代码块

  2. 本类的静态代码块

  3. 父类的普通代码块

  4. 本类的普通代码块

  5. 父类的构造方法

  6. 本类的构造方法

  • jvm内存模型

  1.  java栈:是线程隔离的,存放对象的地址,方法执行时生成栈帧,每个栈帧都包含一些局部变量、基本类型变量区、操作栈和方法返回值等信息;

  2. 本地方法栈:是线程隔离的,和java栈类似,执行系统方法时使用;

  3. 程序计数器:是线程隔离的,保存当前正在执行的程序的内存地址,为了线程切换后能回到准确的执行位置,每个线程都需要有一个独立的程序计数器,各个本地方法栈:是线程隔离的,和java栈类似,执行系统方法时使用;

  4. 堆:线程共享,在jvm启动时创建,存储对象,包括对象的属性,对象方法的地址等,java堆是垃圾回收器(GC)管理的主要区域,是线程不安全的;

  5. 方法区:线程共享,方法区存放了要加载的类的消息(名称、修饰符等)、类的静态变量、final类型常量、类的属性信息、类的方法信息等,代表一个类的Class对象,并且包括常量池(java8常量池从方法堆中移除,方法区使用元空间实现)。方法区也是堆的一部分,即是java堆中的永久代Permanet Generation,存储相对稳定。
     

    Java面试知识点笔记_第4张图片

     

  • jvm垃圾回收机制

垃圾回收机制:不同的对象生命周期不同,采用分代垃圾回收,把不同生命周期的对象放在不同的代上,不同的代上采用适合的垃圾回收方式进行回收。

jvm共划分为三个分代空间:年轻代(Young Generation)、年老代(Old Generation)和持久代(Permanent)

  1. 年轻代:存放所有新生成的对象,该空间的垃圾回收会很频繁,因此其垃圾回收算法更加注重回收率,称为次收集(minor gc),采用复制算法,即复制到老年代
  2. 年老代:年轻代堆空间长期存活对象(经过n轮垃圾回收后还存活,默认15次)会转移到年老代堆空间,故都是一些生命周期比较长的对象。这个堆空间通常比年轻代堆空间大,并且其空间增长速度比较缓慢。由于年老代堆空间大,因此其垃圾回收算法需要更节省时间,需要能处理低垃圾密度的对空间。称为全收集(full gc),采用标志清除算法
  3. 持久代:用于存放静态文件,类的静态变量,以及java类的元数据

垃圾回收方式有两种:次收集(minor gc)和全收集(full gc)

  1. 次收集:当年轻代空间紧张时会被触发,将还存活的对象移到年老代堆空间,相对于全收集而言,收集时间较短
  2. 全收集当年老代或持久代堆空间满了,会触发全收集操作,会覆盖全范围的对象堆,时间较长。使用System.gc()方法可以显示地启动全收集。

垃圾回收器种类:

  1. 串行收集器:单线程,暂停所有应用的线程来工作
  2. 并行收集器:默认的垃圾回收器,多线程,也需要暂停所有应用的线程来工作
  3. G1回收器:用于大堆区域,堆内存分割,并发回收
  4. CMS回收器:多线程扫描,标记需要回收的实例,清除

分布式垃圾回收(DGC):

  • 概念:java虚拟机中,一个远程对象不仅会受到本地虚拟机内的变量的引用,还会被远程引用,只有当一个远程对象不受到任何本地引用和远程引用,这个远程对象才会结束生命周期。
  • 服务端的一个远程对象在三个地方被引用:
  1. 服务端的一个本地对象持有它的本地引用

  2. 客户端获得远程对象的存根对象,也就是说,客户端持有它的远程引用

  3. 服务端的远程对象已经注册到rmiregistry注册表中,也就是说,rmiregistry注册表持有了它的远程引用

  • 服务端判断客户端是否持有远程对象引用的方法:
  1. 当客户端获得一个远程对象存根时,就会向服务器方式一条租约(lease)通知,以告诉服务器自己持有了这个远程对象的引用

  2. 租约是有期限的,如果租约到期了,服务器则认为客户端不再持有远程对象的引用

  3. 客户端定期向服务端发送租约通知,以保证服务器知道客户端一直持有者远程对象的引用
     

  • 类的加载机制

jvm把class文件中的类描述数据从文件加载到内存,并对数据进行校验、解析、初始化,使这些数据最终成为可以被jvm直接使用的java类型,这个过程叫做java的类加载机制

class文件中的“类”从加载到jvm内存中,到卸载出内存过程有以下七个声明周期阶段:

  • 加载:主要完成下面三件事:

  1. 通过一个类的全限定名来获取这个类的二进制字节流(class文件)。获取的方式可以通过jar包、war包、网络中获取、jsp文件生成等方式;

  2. 将这个字节流所代表的静态存储结构转化为方法区的的运行时数据

  3. 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口,存放在方法区。   

  • 验证:验证被加载后的类是否有正确的结构,类数据是否符合虚拟机的要求,确保不会危害虚拟机安全

  • 准备:为静态变量在方法区分配内存,并赋默认初值(0或null),静态常量直接初始化,如statit int a=100,在准备阶段a

  • 解析:将类的二进制数据中的符号引用(因为在class文件中不知道真正的内存地址,所以用符号表示)换成直接引用(指向内存中的地址)

  • 初始化:初始化的主要工作是为静态变量赋程序设定的默认初值,如statis int a=100,在准备阶段a=0,初始化后a=100。有且只有以下五种情况必须对类进行初始化:

  1. new 创建类的实例;

  2. 对类进行反射调用的时候,如果类没有进行过初始化,则要先对类进行初始化;

  3. 当初始化化一个类时,如果它的父类没有初始化,则必须先初始化符类;

  4. 当虚拟机启动时,用户指定一个主类(包含main方法的类),虚拟机会首先初始化这个类;

  5. 当使用jdk1.7的动态语言支持时,java.lang.invoke.MethodHandler实例最后解析的方法句柄,并且这个方法句柄对应的类没有初始化。

  • 使用

  • 卸载

其中,验证、准备、解析统称为链接阶段,加载、验证、准备、初始化、卸载的开始顺序是确定的,但进行与结束的顺序不一定。

  • 类加载器与双亲委派模型

判断两个类相等需要满足下面三个条件:

  1. 同一个class文件

  2. 同一个类加载器加载

  3. 同一个虚拟机加载

类加载器分类:

    1. 启动类加载器(Bootstrap ClassLoader):负责加载java的核心类库,如%JAVA_HOME%/lib目录下的rt.jar。根类加载器非常的特殊,它不是ClassLoader的子类,由C/C++实现,不能直接通过引用进行操作;
    2. 扩展类加载器(Extension ClassLoade):由ExtClassLoader实现,它负责加载扩展目录%JAVA_HOME%/jre/lib/ext目录下的jar包,开发者可以直接使用扩展类加载器;
    3. 应用类加载器(Application ClassLoader):由AppClassLoader实现,它负责加载应用CLASSPATH下的jar包与类路径,开发者定义的类一般由此加载器加载;
    4. 自定义类加载器(UserCustom ClassLoader):用户自己定义的类加载器,如实现从指定场所(网络、数据库)加载类

类加载器之间的层次关系被称为类加载器的双亲委派模型, 该模型要求除了顶层的启动类加载器外,其他的类加载器都应该有自己的父类加载器,而这种父子关系一般通过组合关系来实现,而不是通过继承。    

Java面试知识点笔记_第5张图片

                                                              

双亲委派加载机制:当一个类收到了类加载请求,它首先不会尝试自己去加载这个类,而是把这个加载任务委派给父类去完成,每一个层次类加载器都是如此,因此所有请求都应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。

Java面试知识点笔记_第6张图片

                                  

 

双亲委派模型的作用:防止内存中出现多份同样的字节码,即确保同一个类不会被加载两次。

  • 能不能自己写一个类叫java.lang.System

通常不可以,因为jvm通过全限定名加载类,即使我们写了这样一个类,由于类加载器的双亲委派模型,我们自己写的类也不会被加载到(系统的System已经先被加载了),这时我们可以自定义一个类加载器,并把该类放到一个其他加载器加载不到的地方,这样就可以由自定义的类加载器来加载该类。

  • Class.forName()和Class.loadClass()的区别

  1. Class.forName():除将类的.class文件加载到jvm中外,还会对类进行解释,执行类的static代码块(也有可以控制是否执行的重载方法);

  2. Class.loadClass():只是将类的.class文件加载到jvm中,不会执行static中的内容,只有在newInstance(反射)才会去执行static块;

  • java的分派原则

概念:

  • 根据对象的类型而对方法进行的选择,就是分派;
  • 变量被声明时的类型叫做变量的静态类型
  • 变量所引用的对象的真实类型叫做变量的实际类型,如Person man = new Man(),Person是静态类型,Man是实际类型;
  • 根据分派的发生的时期,可以将分派分为静态分派动态分派
  1. 静态分派发生在编译期,分派根据静态类型消息发生,方法重载就是静态分派,编译时多态,还有静态方法也是;

  2. 动态分派发生在运行期,分派根据实际类型定位方法执行版本,方法重写就是动态分派,运行时多态(普通方法);

  • 一个方法所属的对象叫做方法的接收者,方法的接收者与方法的参数统称为方法的宗量
  • 根据分派可以基于多少种宗量,可以将分派分为单分派和多分派:
  1. 单分派根据一个宗量的类型(实际类型)进行方法的选择

  2. 多分派根据多于一个宗量的类型进行选择

  • java是一种静态多分派(重载),动态单分派(重写)的语言,因为:
  1. 动态分派编译器就确定了目标方法的参数类型(如重写,方法参数确定),唯一可以影响到虚拟机选择的因素只有此方法的接收者的实际类型。

  2. 静态分派选择依据为方法接收者的静态类型方法参数的类型

  • java的反射机制

在运行状态中,对于任意一个类,都能知道这个类的属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取消息及动态调用对象方法的功能叫做java的反射机制。

获得Class对象的三种方式:

    1. Object的getClass()方法
    2. 静态的.class属性
    3. Class类的静态方法:Class.forName()

优点:

    1. 动态创建对象和编译,增加程序的灵活性,避免将程序写死在代码里;
    2. 可以通过反射直接访问对象的私有属性或方法

缺点:

    1. 反射操作的效率比那些非反射操作低的多,所以使用反射会降低性能
    2. 安全限制,反射要求程序必须在一个没有安全限制的环境中运行
    3. 反射允许执行一些在正常情况下不被允许执行的操作(如访问私有属性和方法)   
  • java8的新特性

  • lambda表达式和函数式接口(只有一个函数的接口)
  • 接口的默认实现方法和静态方法
  • 重复注解@Repeatable
  • 流处理Stream,对集合对象功能的增强
  • 类型推断,
  • Date/Time API
  • 方法引用(无参方法),分为:
  1. 构造器引用:Object::new

  2. 普通方法:Object::a

  3. 静态方法引用:Class::a

  

 

5. 数据存储

  • 数据库五大约束

    1. 主键约束:唯一性、非空型;
    2. 唯一约束:唯一性,可以空;
    3. 默认约束:设置数据默认值;
    4. 外键约束:需要建立两张表之间的联系;
    5. 非空约束:字段不能为空   
  • MySQL 索引使用的注意事项

  1. 索引实际上也是一张表,保存着主键或索引字段,以及一个能将每个记录指向实际表的指针,数据库需要维护索引表,所以insert和update操作在有索引的表中会花费更长的时间,因为同时需要更新索引表
  2. 索引的类型:
    • unique:唯一索引,不可以有相同值,可以有NULL
    • index:普通索引,允许出现相同的索引内容
    • promary key:主键索引,不允许出现相同的值,属于聚集索引
    • fulltext index:全文索引,可以针对值中的某个单词,但效率确实不敢恭维
    • 组合索引:实质上是    将多个字段建到一个索引里,列值的组合必须唯一
  1. 索引不会包含NULL列, 只要列中包含有NULL值,都将不会被包含在索引中
  2. 更新频繁的列不应设置索引
  3. 数据量小的表不要使用索引
  4. 重复数据多的字段不应设为索引
  5. 使用短索引, 对串列进行索引,如果可以就应该指定一个前缀长度
  6. mysql查询只使用一个索引,因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的
  7. like ‘%aaa%’不会使用索引,而like ‘aaa%’可以使用索引
  8. 不要在索引列上进行运算
  9. 不使用NOT IN 、<>、!=操作,但<,<=,=,>,>=,BETWEEN,IN是可以用到索引的
  10. 在where和join中出现的列需要建立索引
  • 索引的优缺点

优点:

    1. 大大加快数据的检索速度
    2. 唯一索引可以保证表中每行数据的唯一性
    3. 加速表和表之间的连接    
    4. 在使用分组和排序子句进行数据检索时,可以显著减少查询中分组和排序的时间

缺点:

    1. 索引需要占用数据表以外的物理存储空间
    2. 创建索引和维护索引需要花费一定的时间
    3. 对表进行更新操作(增、删、改)时,索引需要被重建,降低了数据的维护速度
  • 聚集索引与非聚集索引的区别

聚集索引和非聚集索引根本的区别是表记录的排列顺序和索引的排列顺序是否一致

    1. 聚集索引的表记录排列顺序和索引的顺序一样,所以查询效率快,缺点是修改慢,因为为了保证表中记录的排列顺序和索引顺序一致,在记录插入的时候,会对数据页重新排序。聚集索引的查询效率比非聚集索引高,但写入性能比非聚集索引低,一个表只能有一个聚集索引;
    2. 非聚集索引制定了表中记录的逻辑顺序,记录的顺序和索引不一定一致,两种索引都采用B+树结构,非聚集索引层次多,不会造成数据重排
  • 组合索引

  1. 组合索引:由多个列构成的索引,如create index idx_detp on detp(col1,col2,col3);
  2. 组合索引中有一个引导列,上面col1就是引导列,查询时where子句必须要有引导列col1才会使用索引。因为根据最左匹配原则实际上mysql会创建以下三个索引:(col1),(col1,col2),(col1,col2,col3)
  • sql优化

    1. 对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引;
    2. 尽量避免在 where 子句中对字段进行 null 值判断或使用in 、 not in、!= 或 <> 操作符,否则将导致引擎放弃使用索引而进行全表扫描;
    3. 尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描;
    4. Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调用会引起明显的性能消耗,同时带来大量日志;
    5. 对于多张大数据量(这里几百条就算大了)的表JOIN,要先分页再JOIN,否则逻辑读会很高,性能很差;
    6. 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销;
    7. 任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段;
    8. 尽量避免大事务操作,提高系统并发能力;
    9. 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理
  • 分库分表

  • 水平拆分:即把表的数据按某种规则(比如按ID散列)切分到一个或多个数据库(server)上,水平拆分适用于数据库表并不多,而单张表的数据量很大;

  • 垂直拆分:即把关系紧密(比如同一模块)的表切分出来放在不同的server上,垂直拆分使用于因为表多而数据多的情况;

  • 一般情况都是水平拆分和垂直拆分结合起来,先进行垂直拆分,再对高负载的server进行水平拆分;

  • 分表分库中间件mycat

  • 分库策略:

  1. 根据数值范围,比如用户Id为1-9999的记录分到第一个库,10000-20000的分到第二个库,以此类推。

  2. 根据数值取模,比如用户Id mod n,余数为0的记录放到第一个库,余数为1的放到第二个库,以此类推。

  • 分库分表带来的问题:

  1. 事务问题:
              使用分布式事务:交给数据库管理,简单有效,但性能代价高,特别是shard越来越多时;
              由应用程序和数据库共同控制:将一个跨多个数据库的分布式事务分拆成多个仅处 于单个数据库上面的小事务,并通过应用程序来总控 各个小事务。

  2. 跨节点join问题:分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。

  3. ID问题:不能再依赖数据库自身的主键生成机制,使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题

  4. 跨分片的排序分页:需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序

  • MySQL 遇到的死锁问题

死锁一般是两个操作都要等待对方释放锁后才会释放里一个对象需要的锁,或者形成环路,导致锁无法释放。

mysql的造成死锁有以下几种情况:

    1. 不同表的相同记录行锁冲突:a事务执行delete from table_1 where id=1获得锁a,b事务执行delete from table_2 where id=2获得锁b,然后a事务也要执行delete from table_2 where id=2,这时候需要获得锁b,而因为b事务还要执行delete from table_1 where id=1,又需要获得锁a,这是,就导致锁a和锁b都不被释放,形成死锁;
    2. 相同表记录行冲突:跟上一种情况相似,都是两个事务都需要获得对方持有的锁,导致都无法释放锁;
    3. 不同索引锁冲突:事务A在执行时,除了在二级索引加锁外,还会在聚簇索引上加锁,在聚簇索引上加锁的顺序是[1,4,2,3,5],而事务B执行时,只在聚簇索引上加锁,加锁顺序是[1,2,3,4,5],这样就造成了死锁的可能性;

如何尽量避免死锁:

    1. 以固定的顺序访问表和行,避免出现交叉等待锁的情况;
    2. 大事务拆小,大事务更倾向于死锁,如果业务允许,将大事务拆小。
    3. 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁概率。
    4. 降低隔离级别,可以避免掉很多因为gap锁造成的死锁
    5. 为表添加合理的索引,如果不走索引将会为表的每一行记录添加上锁,死锁的概率大大增大
  • 为什么要用 B-tree结构

  • 主要是减少读写磁盘的次数,磁盘与主存的速度差距巨大,b树能很大减少读写磁盘的次数;

  • B树是很严格的平衡二叉搜索树(叶子节点深度都相同),B树树深较小,读写磁盘次数大大减少。

  • B树插入和删除的时候,是采用沿途分裂和沿途和并的方式进行的,和查找时,读写磁盘的次数差不多

  • 并且InnoDB引擎在B+树叶子节点层添加了顺序访问的指针,从而加快顺序访问的速度
     

  • limit 20000 加载很慢怎么解决

limit当数目过大时,数据读取会很慢,limit10000,20的意思扫描满足条件的10020行,扔掉前面的10000行,返回最后的20行,问题就在这里,优化:

  1. 子查询优化法:先找出第一条数据,然后大于等于这条数据的id就是要获取的数据,如:select * from Member where id>= (select id from Member limit 10000,1) limit 20;但是数据必须是连续的,可以说不能有where条件,where条件会筛选数据;

  2. 倒排表优化法:倒排表法类似建立索引,用一张表来维护页数,然后通过高效的连接得到数据,但只适合数据数固定的情况,数据不能删除,维护页表困难;

  3. 反向查找优化法:偏移超过一半记录数的时候,先用排序,这样偏移就反转了,但order by优化比较麻烦,要增加索引,索引影响数据的修改效率,并且要知道总记录数 ,偏移大于数据的一半;

  4. limit限制优化法:把limit偏移量限制低于某个数。。超过这个数等于没数据

  • 事务

  • 事务的ACID特性:

  1. 原子性(Atomicity):改变数据状态要么是一起完成,要么一起失败

  2. 持久性(Durable):事务提交后,数据必须以一种持久性方式存储起来

  3. 隔离性(Isolation):事务以相互隔离的方式执行,即使有并发事务,彼此之间也影响

  4. 一致性(Consistency):数据的状态是完整一致的

  • 事务的4种隔离级别:

  1. ReadUncommitted:读未提交,一个事务可以读取另一个未提交事务的数据,即脏读

  2. ReadCommitted:读提交,一个事务要等另一个事务提交后才可以读取数据,可以解决脏读,但是还存在不可重复读问题,即一个事务内两个相同的查询却返回了不同的数据,因为在这两次查询中可能有另一个事务update修改了数据;

  3. Repeatable read:重复读,就是在读取数据时,开启事务,不允许其他事务update,重复读可以解决不可重复读问题,但还是存在幻读,即一个事务两次查询之间另一个事务insert插入了数据,导致第二次查询出的数据多了一条记录;

  4. Serializable:序列化,Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读和幻读问题,但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用

  • 事务的传播行为:

  1. PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务
    比如:方法B的事务级别是PROPAGATION_REQUIRED,方法A中调用方法B,这是如方法A已经起了一个事务,方法B就不会新起事务,而是处于A方法的事务中,如果A方法没有事务,则B方法会新起一个事务

  2. PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
    和上面不同的是,如果方法A没有事务,则方法B以非事务方式执行

  3. PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常
    如果方法A没有事务,则方法B抛出异常,即它只能被父事务调用

  4. PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起
    方法B会新建一个事务,方法A的事务会被挂起,等待方法B的事务执行完后,方法A的事务才继续执行。相当于两个事务

  5. PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
    方法B以非事务方法执行,方法A的事务被挂起,等待方法B执行完,方法A的事务才执行

  6. PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
    方法B不支持在事务当中执行,即如果方法A有事务,则会抛出异常

  7. PROPAGATION_NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作

Java面试知识点笔记_第7张图片

               

 

6. 缓存使用

  • redis有哪些常用的数据类型

  1. String:string可以包含任何数据,比如jpg图片(生成二进制)或者序列化的对象、json字符串等、序列化的二进制对象等;

  2. Hash:hash是一个string类型的field和value的映射表,相当于hashmap,采用key-field-value的方式,一个key可对应多个field,一个field对应一个value,默认当hash成员个数不超过64个时采用线性紧凑格式存储,超过该值自动转成真正的HashMap。hash也可以用来存储对象的值;

  3. List:list是一个链表结构,每个子元素都是string类型的双向链表,主要功能是push和pop,从链表的头部或者尾部添加删除元素,这样list既可以作为栈,又可以作为队列;

  4. Set:set是一个string类型的无序集合,通过hash table 实现,不允许插入重复元素;

  5. SortedSet:有序集合,一列存 value,一列存顺序,插入元素时可以指定元素插入的位置;

  • redis使用场景

  1. 缓存:缓存热数据,经常会被查询,但是不经常被修改或者删除的数据;

  2. 计数器:计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能,不过要注意持久化;

  3. 队列:队列不仅可以把并发请求变成串行,并且还可以做队列或者栈使用;

  4. 分布式锁:验证前端的重复请求,秒杀系统,全局增量ID生成

  5. 位操作:大数据处理;

  6. 最新列表:例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种low货,尝试redis的 LPUSH命令构建List;

  7. 排行榜:用sortedset有序集合;

  • Redis 持久化机制

  • RDB持久化:RDB快照模式,在指定的时间间隔内生成数据集的时间点快照,Redis 将数据库快照保存在名字为 dump.rdb 的二进制文件中;

          优点:

                 1、利于灾难恢复,快照模式只有一个rdb文件,它保存了 Redis 在某个时间点上的数据集,方便传送到别的数据中心;

                        2、性能最大化,父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作;

                        3、RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

                 缺点:

                        1、rdb不能最大限度地避免数据丢失,因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失;

                        2、数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端。

  • AOF持久化AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集,AOF 文件是一个只进行追加操作的日志文件;
               优点:
                      1、AOF可以带来更高的数据安全性,aof同步策略有每修改同步、每秒同步和不同步,就算发生故障停机,也最多只会丢失一秒钟的数据;

                          2、AOF 文件是一个只进行追加操作的日志文件,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容;

                          3、Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合;

                          4、AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操,可以通过该文件完成数据的重建。

                    缺点:

                           1、对于相同数量的数据集而言,AOF文件通常要大于RDB文件;

                           2、根据同步策略的不同,AOF在运行效率上往往会慢于RDB。

  • 同时使用RDB和AOF,这种情况下,当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整,当然也可以关闭持久化功能,让数据只在服务器运行时存在。

  • Redis 为什么是单线程的

因为redis所有数据都是存储在内存中,而cpu操作内存的速度是非常快速的,可能远小于多线程时线程上下文切换的时间,所以单线程的效率会更高,省去了上下文切换的代价;

而且在单线程基础上任何原子操作都可以几乎无代价地实现,而多线程下一些操作需要进行加锁,而降低效率

  • 高并发下的缓存问题

  • 缓存雪崩:短时间内,大面积缓存到过期,导致大量原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力;

措施:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,或在原有的失效时间加上随机值,避免短时间内,大量缓存过期;

  • 缓存穿透:该key被高并发访问,缓存访问没有命中,从而导致了大量请求达到数据库;

措施:采用布隆过滤器,所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力;

  • 缓存预热:缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统;
  • 缓存降级:当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。

  • Redis 集群方案与实现

  • 客户端分片:通过Redis客户端预先定义好的路由规则,把对Key的访问转发到不同的Redis实例中,最后把返回结果汇集;

优点:所有的逻辑都是可控的,不依赖于第三方分布式中间件;

缺点:增加或者减少Redis实例的数量,需要手工调整分片的程序,可维护性差;

  • twemproxy:twitter开源的redis代理,Twemproxy通过引入一个代理层,将多个Redis实例进行统一管理,Twemproxy根据路由规则发送到正确的Redis实例,最后Twemproxy把结果汇集返回给客户端。

优点:客户端像连接Redis实例一样连接Twemproxy,不需要改任何的代码逻辑;支持无效Redis实例的自动删除;Twemproxy与Redis实例保持连接,减少了客户端与Redis实例的连接数;

缺点:由于Redis客户端的每个请求都经过Twemproxy代理才能到达Redis服务器,这个过程中会产生性能损失;Twemproxy无法平滑地增加Redis实例,维护性差;

  • Codis:Codis是一个支持平滑增加Redis实例的Redis代理软件,包含以下四个部分:

  1. codis proxy:Redis客户端连接到Redis实例的代理,Codis Proxy是无状态的,可以用Keepalived等负载均衡软件部署多个Codis Proxy实现高可用

  2. zookeeper:分布式的、开源的应用程序协调服务,Codis依赖于ZooKeeper存储数据路由表的信息和Codis Proxy节点的元信息

  3. codis config:Codis管理工具。可以执行添加删除CodisRedis节点、添加删除Codis Proxy、数据迁移等操作

  4. codis redis:Codis项目维护的Redis分支,添加了slot和原子的数据迁移命令

  • redis 3.0集群:Redis 3.0集群采用了P2P的模式,完全去中心化。Redis把所有的Key分成了16384个slot,每个Redis实例负责其中一部分slot。

优点:部署简单,不依赖其他组件;

缺点:业务升级困难,对协议进行了较大的修改,对应的Redis客户端也需要升级

 

 

7. 消息队列

  • 消息队列的使用场景   

  1. 异步处理:比如用户注册后,需要发注册邮件和注册短信,发送邮件和短信就可以通过消息队列进行异步处理,提高了系统的吞吐量;

  2. 应用解耦:不同应用间通过消息队列来完成通信与数据处理,比如订单系统和库存系统就可以通过消息队列来很好的解耦;

  3. 流量削峰:用消息队列缓解短时间内的高流量爆发,比如秒杀活动,短时间内流量暴增,很可能会压垮服务器,这是可以先把用户请求写入消息队列,再从消息队列中读取请求处理

  4. 日志处理:日志处理是指将消息队列应用在日志处理中,比如kafka的应用;

  5. 消息通讯:比如实现点对点消息队列,或者聊天室等;

  • 消息的幂等性

  1. 为了保证消息必达,MQ使用了消息超时、重传、确认机制。不管是client没有收到server的ack还是server没有收到client的ack,都会使得消息可能被重复发送,这时就要保证系统的幂等性,包括mq server内部和client消费者。

  2. mq内部的幂等性:MQ内部生成一个全局唯一、与业务无关的消息ID:inner-msg-id,可以根据inner-msg-id判断消息是否重复接收;

  3. 消息消费者的幂等性:生成一个唯一ID标记每一条消息,将消息处理成功和去重日志通过事物的形式写入去重表;

  •  消息的堆积解决思路

 对消息进行持久化处理,如存储在内存里,存储在分布式KV里,存储在磁盘文件里,存储在数据库里等,持久化的形式能更大程度地保证消息的可靠性(如断电等不可抗外力),并且理论上能承载更大限度的消息堆积(外存的空间远大于内存)

 

 

 

 

8. Spring

  • BeanFactory 和 ApplicationContext 有什么区别

  1. 利用MessageSource进行国际化 ,BeanFactory不支持国际化,ApplicationContext扩展了MessageResource接口,具有消息的处理能力(i18N);

  2. applicationContext加入了强大的事件机制;

  3. applicationContext扩展了ResourceLoader(资源加载器)接口,从而可以用来加载多个Resource;

  4. 对web应用的支持不一样;

  5. .BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化;ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean;

  • Spring Bean 的生命周期

  1. 实例化BeanFactoryPostProcessor实现类

  2. 执行BeanFactoryPostProcessor的postProcessBeanFactory方法

  3. 实例化BeanPostProcessor实现类    

  4. 实例化InstantiationAwareBeanPostProcessor实现类

  5. 执行InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法

  6. 执行Bean的构造方法

  7. 执行InstantiationAwareBeanPostProcessor的postProcessPropertyValues方法

  8. 为bean注入属性

  9. 调用BeanNameAware的setBeanName方法

  10. 调用BeanFactoryAware的setBeanFactory方法

  11. 执行BeanPostProcessor的postProcessBeforeInitialization方法

  12. 执行InitializingBean的afterPropertiesSet方法

  13. 调用的init-method属性指定的初始化方法

  14. 执行BeanPostProcessor的postProcessAfterInitialization方法 

  15. 执行InstantiationAwareBeanPostProcessor的postProcessAfterInstantiation方法

  16. 容器初始化成功,执行业务代码后,下面销毁容器

  17. 调用DiposibleBean的Destory方法

  18. 调用的destory-method属性执行的销毁方法         

  • Spring IOC 如何实现

  • Ioc:控制反转,控制反转是指获得依赖对象的方式反转,创建对象的控制权交给第三方容器管理,另一种说法是依赖注入(DI),动态的向某个对象提供它所需要的其他对象 ,spring通过java的反射机制和工厂模式来实现Ioc;   
  • 通过xml配置的方式实现IOC:

  1. xml配置文件中配置要注册bean的信息,包括beanName,class,init-method,detory-method,property,scope(作用域)等;

  2. spring工厂类读取配置文件,根据配置信息通过反射机制创建类的实例,并通过setter方法给属性赋值;

beanfactory是最基本的Ioc容器接口,定义了Ioc容器的基本功能规范,最终的默认实现类是DefaultListableBeanFactory,beanfactory采用懒加载模式;

  • 通过自动装配来实现依赖注入,spring提供了一系列的注解来实现bean的自动装配:

  1. @Autowired:根据类型注入,如果需要按名称进行装配,则需要配合@Qualifier;

  2. @Autowired注解是由spring提供,@Resource是由J2EE提供,所以,如果要减少对spring的依赖,尽量用@Resource注解;

  3. @Resource:默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入;

  • Spring AOP 实现原理

spring aop通过代理方式实现在方法调用前后插入执行相应逻辑,spring aop的代理方式主要有JDK的动态代理和CGLIB的动态代理:

    1. JDK动态代理:JdkDynamicAopProxy先获取拦截器,判断拦截器链是否为空,如果是空的话直接调用切点方法;如果拦截器不为空的话那么便创建ReflectiveMethodInvocation类,把拦截器方法都封装在里面,也就是执行getInterceptorsAndDynamicInterceptionAdvice方法。只能对实现了接口的类生产代理,不能针对类
    2. CGLIB动态代理:也叫自类代理,通过动态创建子类对象实现对目标对象功能的拓展,目标对象不用实现接口

Aop概念:

  • 切面(aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”
  • 连接点(joinPoint):程序执行中的某一行为             

  • 通知(advice):切面对于某个连接点所产生的动作

  1. 前置通知:在连接点执行之前的通知

  2. 抛出异常后通知:在方法抛出异常退出时执行的通知
  3. 环绕通知:包围一个连接点的通知,可以在方法的调用前后完成自定义的行为,也可以选择不执行

  4. 返回后通知:在某个连接点正常完成执行后的通知,不包括排除异常的情况

  5. 后置通知:在连接点执行之后的通知

  • 切入点(pointCut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联
  • 目标对象(target object):被一个后多个切面所通知的对象

  • AOP代理(aop Proxy):spring aop有两种代理,jdk代理和cglib代理

Aop可以有效降低模块之间的耦合度,使系统容易扩展,更好的代码复用

  • Spring 事务底层原理

Spring事务处理,是将事务处理的工作统一起来,并为事务处理提供通用的支持。

工作原理:

    1. 划分处理单元----IOC:ioc划分了处理单元,并将对事务的各种配置放到ioc容器中;
    2. AOP拦截需要进行事务处理的类,用TransactionProxyFactoryBean接口来使用AOP功能,生成proxy代理对象,通过TransactionInterceptor完成对代理方法的拦截,将事务处理的功能编织到拦截的方法中;    
    3. 对事务处理实现(事务的生成、提交、回滚、挂起):spring委托给具体的事务处理器实现,实现了一个抽象和适配器,适配具体的事务处理器;

spring事务源码解析:                             

Java面试知识点笔记_第8张图片

 

 

  • spring mvc的运行流程

  1. spring mvc将所有请求都提交给DispatcherServlet,它会将请求交给其他模块负责处理

  2. DispatcherServlet查询一个或多个HandlerMapping,找到处理请求的Controller

  3. Controller进行业务处理后,会返回一个ModelAndView    

  4. DispatcherServlet查询一个或多个试图解析器,找到ModelAndView对象指定的视图对象        

  5. 视图对象负责渲染和返回给客户端           

  • spring的单例实现原理

Spring为实现单例类可继承,使用的是单例注册表的方式:

    1. 使用一个HashMap来当注册表
    2. 在静态代码块中实例化对象保存在map中    
    3. 设置构造器属性为protected
    4. 利用了classloader的机制来保证初始化instance时只有一个线程    
  • Spring 框架中用到了哪些设计模式

  1. 工厂模式:将对象的创建和使用分离,即应用程序将对象的创建和初始化工作交给工厂对象,这样spring管理的就不是普通bean,而是工厂bean;

  2. 单例模式:保证一个类只有一个实例,并提供一个访问它的全局访问点,spring默认情况下的bean都是单例;  

  3. 适配器模式:spring mvc中就有用到适配器,由于Controller的类型不同,有多重实现方式,那么调用方式就不是确定的,因此利用适配器代替controller执行相应的方法;

  4. 代理模式:为其他对象提供一种代理以控制对这个对象的访问,spring aop中用到了jdk代理和cglib代理;

  5. 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新,常用的地方是listener的实现;

 

 

9. Mybatis

  • #{}和${}的区别是什么?

  1. ${}是Properties配置文件的变量占位符,它可以用于标签属性值和sql内部,属于静态文本替换

  2. #{}是sql的参数占位符,Mybatis会将sql中的#{}替换为?号

  • Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

  1. Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值

  2. Dao接口的工作原理就是jdk动态代理,Mybatis运行时会使用jdk代理为dao接口生成proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回

  3. Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略

  • Mybatis是如何进行分页的?分页插件的原理是什么?

  1. Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页

  2. 分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的sql,然后重写sql,根据dialect方言,添加对应的物理分页语句和物理分页参数

  • Mybatis动态sql有哪些,原理是什么?

  1. Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind

  2. 其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能

  • Mybatis的延迟加载

  1. 延迟加载即先从单表查询,需要时再从关联表去关联查询

  2. resultMap可以实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具备延迟加载功能

  3. 延迟加载可以大大提高数据库性能,可以有效解决n+1问题

  • Mybatis和hibernate的区别

  1. mybatis时半自动的,仅有基本的字段映射,需要自己手写sql,hibernate是全自动的,可以根据javabean和数据库的映射结构自动生成sql

  2. hibernate的数据库移植性远大于mybatis,hibernate通过其强大的映射结构和hql,大大降低了对象与数据库的耦合

  3. sql的直接优化上面,mybatis要比hibernate方便很多

  4. hibernate有完善的二级缓存机制

  •  

 

 

 

10. Netty

  • Netty的使用场景

  1. Netty 作为异步高性能的通信框架,往往作为基础通信组件被一些 RPC 框架使用,如dubbo;

  2. Netty也经常用作推送服务;

  3. Netty还可以用来开发各种服务器

  • 原生的 NIO 在 JDK 1.7 版本存在 epoll bug

java 原生NIO的空轮询bug,若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,最终导致CPU 100%。Netty解决办法:

    1. 对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数
    2. 若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug
    3. 重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,并将原来的Selector关闭。
  • Netty 线程模型

Netty使用reactor反应堆线程模型,reactor模式是事件驱动的,有一个或多个并发输入源,有一个事件分离器,有多个事件处理器;这个事件分离器会同步的将输入的请求(Event)多路复用的分发给相应的事件处理器。

NioEventLoop是Netty的Reactor线程,它在Netty Reactor线程模型中的职责如下:

    1. 作为服务端Accpector线程,负责处理客户端的请求接入;
    2. 作为客户端Connector线程,负责注册监听连接操作位,用于判断异步连接结果;
    3. 作为IO线程,监听网络读操作位,负责从SocketChannel中读取报文;
    4. 作为IO线程,负责向SocketChannel写入报文发送给对方,如果发生写半包,会自动注册监听事件,用于后续继续发生半包数据,知道数据全部发送完成。
  • Netty的零拷贝(Zero Copy)

所谓零拷贝,就是在操作数据时,不需要将数据buffer从一个内存区域拷贝到另一个内存区域,因为少了一次内存的拷贝,cpu的效率得到了提高。

  1. Netty提供了CompositBytebuf类,它可以将多个ByteBuf合并为一个逻辑上的ByteBuf,避免了各个ByteBuf之间的拷贝
  2. 通过wrap操作,我们可以将byte数组、ByteBuf、ByteBuffer等包装成一个Netty ByteBuf对象,进而避免了拷贝操作
  3. ByteBuf支持silce操作,因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf, 避免了内存的拷贝
  4. 通过FileRegion包装的FileChannel.tranfeTo实现文件传输,可直接将文件缓冲区的数据发送到目标channel,避免了传统的通过循环write的方式导致的内存拷贝问题
  • Netty的工作流程

服务端步骤

    1. 建立服务端监听套接字ServerSocketChannel,以及对应的管道pipeline
    2. 启动boos线程,将ServerSocketChannel注册到boss线程持有的selector中,并将注册返回的selectionKey赋值给ServerSocketChannel关联的selectionKey变量
    3. 在ServerSocketChannel对应的管道中出发channelRegisted事件
    4. 绑定IP和端口
    5. 触发channelActive事件,将ServerSocketChannel关联的selectionKey的OP_ACCEPT置为1
    6. 客户端发起connect请求后,boos线程正在运行的select循环检测到该ServerSocketChannel的ACCEPT事件就绪,则通过accept系统调用建立一个已连接套接字SocketChannel,并为其创建相应的管道
    7. 在服务端监听套接字对应的管道中触发channelRead事件
    8. ChannelRead事件由ServerBootstrapAcceptor的channelRead方法响应:为已连接套接字对应的管道加入ChannelInitializer处理器,启动一个worker线程,并将已连接套接字的注册任务加入到work线程的任务队列
    9. work线程执行已连接套接字的注册任务:
    10. 在work线程运行的同时,Boss线程接着在服务端监听套接字对应的管道中触发channelReadComplete事件
    11. 客户端向服务端发送消息后,work线程正在运行的selector循环会检测到已连接套接字的Read事件就绪。则通过read系统调用将消息从套接字的接受缓冲区中读的AdaptiveRecvByteBufAllocator分配到缓存中
    12. 在已连接套接字对应的管道中触发channelRead事件
    13. channelRead事件由EchoServerHandler处理器的channelRead方法响应:执行write操作将消息存储到ChannelOutboundBuffer中
    14. 在已连接套接字对应的管道中触发channelReadComplete事件            
    15. channelReadComplete事件由EchoServerHandler处理器的channelReadComplete方法响应:执行flush操作将消息从ChannelOutboundBuffer中flush到套接字的发送缓冲区中

客户端步骤

  1. 建立套接字SocketChannel,以及对应的管道pipeline
  2. 启动客户端线程,将SocketChannel注册到客户端线程持有的selector中
  3. 触发channelRegisted事件
  4. channelRegisted事件由ChannelInitializer的channelRegisted方法响应
  5. 向服务端发起connect请求,并将SocketChannel关联的selectionKey的OP_CONNECT位置为1 
  6. 开始三次握手,客户端正在运行的select循环检测到了该SocketChannel的CONNECT事件就绪 ,则将关联的selectionKey的OP_CONNECT位置为0,再通过调用finishConnect完成连接的建立
  7. 触发channelActive事件
  8. channelActive事件由EchoChannelHandler处理器的channelActive方法响应:通过调用ctx.writeAndFlush方法将消息发往服务端
  9. 首先将消息存储在ChannelOutboundBuffer中,
  10. 然后将消息从ChannelOutboundBuffer中flush到套接字的发送缓冲区                      
  • Netty心跳和重连

心跳检测

    1. 客户端连接服务端
    2. 在客户端ChannelPipeline中加入一个比较特殊的IdleStateHandler,设置一下客户端的写空闲事件,例如5s;
    3. 当客户端的所有ChannelHandler在4s内没有write事件,则会触发userEventTrigger方法;
    4. 在客户端的userEventTrigger中对应的触发事件下发送一个心跳包给服务端,检测服务端是否还存活,防止服务端已宕机,客户端还不知道;
    5. 同样,服务端要对心跳包做出响应,其实给客户端最好的回复就是“不回复”,这样可以降低服务端的压力,因为最多每5s服务端都会收到来自客户端的心跳信息,那么如果10秒内收不到,服务端可以认为客户端挂了,可以close链路;
    6. 假如服务端因为什么因素导致宕机的话,就会关闭所有的链路链接,所以作为客户端要做的事情就是断线重连;

断线重连

    1. 添加一个重连检测狗ConnectionWatchDog,它继承ChannelInboundHandlerAdapter,属于handler,实现TimerTask
    2. 当连接断开后,执行watchdog的channelInactive方法,通过Timer添加一个定时任务
    3. 在run方法里重连服务器,并在channelfuture中添加一个ChannelFutureListener,监听重连是否成功
    4. 若不成功,执行watchdog连接断开事件 ,添加定时任务,继续重连
  • TCP粘包,拆包及解决方法

  • 粘包:tcp传输数据包时,会发生粘包现象,前后两个不同的数据包粘在了一起,服务端很难处理

  • 拆包:tcp传输数据包时,一个包被拆成两份,一份粘在另一个数据包上,导致一个包数据不全,一个包数据错乱,这种情况下既发生了拆包,也发生了粘包

  • 粘包、拆包发生原因:

    1. 接受数据段的应用层没有及时接收缓冲区中的数据,将会发生粘包;
    2. 要发送的数据小于TCP缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;
    3. 带发生数据大于MSS(最大报文长度),TCP在传输前将进行拆包;
    4. 要发送的数据大于TCP缓冲区剩余空间大小,会发生拆包;
  • 粘包、拆包的解决办法:

    1. 可以在数据包之间设置边界,如添加特殊符号,这样服务端就可以根据特殊符号区分不同包;
    2. 发送端将每个数据包封装成固定长度(不够可以补0),这样服务端每次都取固定长度,就将包区分开来了;
    3. 发送端给每个数据包添加包首部,包首部应该至少包含数据包长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了;

 

 

 

 

11. 微服务

  • 前后端分离

  1. 好处:前后端分离可以让前端和后端解耦,两者可以同时开工,不互相依赖,开发效率更高,而且分工比较均匀,代码也更容易管理

  2. 后端专注于:后端控制层(Restful API) 、服务层和数据访问层

  3. 前端专注于:前端控制层(Nodejs)和视图层

前后端分离模式:

    1. 项目设计阶段,前后端架构负责人将项目整体进行分析,讨论并确定API风格、职责分配、开发协助模式;设计确定后,前后端人员共同制定开发接口;
    2. 项目开发阶段,前后端分离是各自分工,协同敏捷开发,后端提供Restful API,并给出详细文档说明,前端人员进行页面渲染;
    3. 项目测试阶段,API完成之前,前端人员会使用mock server进行模拟测试,后端人员采用junit进行单元测试,不用相互等待,API完成后,前后端再对接测试;
    4. 项目部署阶段,利用nginx做反向代理,即java+nodejs+nginx方式进行
  • RPC 的实现原理

    1. rpc,全称remote procedure call,即远程过程调用,是一种计算机通信协议。用来实现不同机器上的方法远程调用,rpc框架需提供一种透明调用机制让使用者不必显示地区分本地调用和远程调用,rpc框架一般通过动态代理的方式实现透明化远程服务调用。

rpc框架实现步骤

    1. 建立通信:在客户端和服务器之间建立Tcp连接,可以是按需连接(需要调用时建立连接,调用完关闭连接),也可以是长连接(连接长期保存),多个远程过程调用共享一个连接,高效的通信框架如Netty
    2. 服务寻址:要查找远程服务的ip和端口,可以通过其他服务注册和发现的中间件实现,如zookeeper、consule、redis等
    3. 网络传输:分为序列化和反序列化,远程调用时,需要将参数对象等序列化成二进制的形式传输,服务端返回后,需要将返回值反序列成对象进行处理,成熟的序列化方案有protobuf、fastjson等
    4. 服务调用:服务器进行本地调用(通过动态代理)之后得到返回值,需要把返回值发生给客户端,客户端需要进行反序列化操作,恢复为内存的表达方式,才能进行相关处理

rpc调用过程:

    1. 客户端通过本地调用的方式调用服务
    2. 客户端存根接收到调用请求后负责将方法、入参等信息序列化成能够网络传输的信息体
    3. 客户端存根找到远程服务的地址,并将信息通过网络发送到服务端
    4. 服务端存根接收到信息后进行解码(反序列化)
    5. 服务端存根根据解码的结果调用本地的服务进行相关处理
    6. 服务端本地服务执行具体业务逻辑并将处理结果返回给服务端存根
    7. 服务的存根将返回结果重新打包成消息(序列化),并通过网络发送给客户端
    8. 客户端存根接收到信息,并进行解码(反序列化)
    9. 客户端得到最终结果
  • 你怎么理解 RESTful

  • restful提供一种软件架构风格、设计风格,提供了一组设计原则和约束条件,每一个URI代表一种资源,客户端和服务器之间,传递这种资源的某种表现层,客户的通过四个HTTP动词(GET、POST、PUT、DELETE),对服务端资源进行操作,实现“表现层状态转化”。

  • restful优点:

    1. 在API升级过程中URL的改动较小,减少版本管理的工作量
    2. 为前端提供足够的灵活性
    3. 方便做权限控制
    4. 接口和资源一一对应,方便编码
    5. 不需要解释即明白某个接口设计的意图
  • 如何保证接口的幂等性

接口的幂等性是指接口可重复调用,在多次调用的情况下,接口最终得到的结果是一致的。

    1. 全局唯一ID:根据业务的操作和内容生成一个全局ID,在进行操作之前检查全局唯一ID是否已经存在,来判断这个操作是否已经执行
    2. 去重表:使用于在业务中有唯一标志的插入场景中,比如支付场景的订单号
    3. 插入或更新:适用于重复插入,数据根据唯一id判断是否重复插入,如果是则会执行更新
    4. 多版本控制:适用于更新的场景下
    5. 状态机控制:适用于在有状态机流转的情况下
  • CAP和BASE理论

  • cap理论,是指在分布式系统中,不可能同时满足一致性(Consistency)、高可用性(Availability)和分区容错性(artition tolerance)这三个基本需求,最多只能满足其中两项

    1. 一致性:指数据在多个副本之间是否能够保持一致的特性
    2. 分区容错性:分布式系统在遇到任何网络分区故障的时候,仍然需要保证能够对外提供满足一致性和高可用的服务
    3. 高可用性:系统提供的服务必须一直处于可用的状态

对于分布式系统而言,分区容错性是一个最基本的要求,因为分布式系统中的组件必然要部署到不同的节点,必然会出现子网络,网络问题是必然会出现的异常,因此只能在一致性和高可用性之间进行权衡。

  • base理论是指,基本可用(Basically Available)、软状态/柔性事务(Soft-state)和最终一致性(Eventual Consistency),是基于CAP理论演化而来,是对CAP中一致性和高可用性的权衡的结果。

核心思想:即使无法做到强一致性,但每个业务根据自身的特点,采用适当的方式使系统达到最终一致性

    1. 最终一致性:系统中的数据副本在经过一定时间后,最终能够达到一致的状态,不需要实时保证系统数据的强一致性。
    2. 软状态:软状态是指允许系统存在中间状态,并且该状态不会影响系统整体可用性,即允许系统在不同节点副本同步的时候存在延时
    3. 基本可用:指分布式系统在出现故障时,允许损失部分可用性,保证核心可用。比如网页访问量过大时,部分用户提供降级服务

BASE理论通过牺牲强一致性来获得可用性,是ACID的一种替代方案

  • 分布式系统中的数据一致性问题

    1. 强一致性:当更新操作完成后,任何多个后续进程或者线程的访问都会返回最新的更新过的值
    2. 弱一致性:系统并不保证后续的进程或线程的访问都会返回最新的更新过的值
    3. 最终一致性:弱一致性的特定形式,系统保证在没有后续更新的前提下,系统最终返回上一次更新过的值。在没有故障发生的前提下,不一致窗口的时间主要受通信延时影响、系统负载和赋值副本的个数影响。

分布式系统中的最终一致性解决方案:

    1. 规避分布式事务--业务整合:将接口整合到本地执行的方法
    2. eBay模式(消息日志):将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储在本地文件、日志或消息队列中,消息日志方案的核心是保证服务接口的幂等性
    3. 分布式事务:分布式事务有两种解决方式:

           一、优先使用异步消息:使用异步消息Conusmer需要实现幂等,业务逻辑保证幂等或增加去重表等类似实现幂等
           二、有的业务不适合异步消息的方式,事务的各个参与方都需要同步的得到结果。这时候可以在每个参与方的业务数据库的同实例上面放一个事务记录表,由一个中心服务对比三方的记录事务表,做一个最终决定。

原理:将分布式事务转化为多个本地事务,然后通过重试等方式达到最终一致性

                    4. 分布式事务服务DTS方案:DTS是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。

                      一、一个完整的业务活动由一个主业务服务和若干从业务服务组成

                      二、主业务服务负责发起并完成整个业务活动

                      三、从业务服务提供TTC性业务操作

                      四、业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在活动提交时确认所有的两阶段事务的 confirm 操作,在业务活动取消时调用所有两阶段事务的 cancel 操作

  • 分布式锁

  • 分布式锁的特点:

    1. 互斥性,保证在分布式集群中,同一方法在同一时间只能被一台机器上的一个线程执行

    2. 必须是可重入锁,也叫递归锁,是指允许同一个线程多次获取锁,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次获得锁,那么计数器就加一,然后锁被释放两次才被真正释放。

    3. 不会发生死锁,客户端持有锁崩溃后,其他客户端可以获得锁

    4. 有高可用的获得锁和释放锁功能

  • 分布式锁的三种实现方式:

    1. 数据库锁:创建一张锁表,但是不能保证可重入性,可能发生死锁。也可以基于数据库的排他锁来实现分布式锁;
    2. 基于redis的分布式锁:通过redis的setNx操作可以保证锁的互斥性,即只有一个客户端可以获得锁,而设置key的有效期可以保证不会发生死锁,另外,可以设置value为requestId,保证加锁和解锁的客户端一致,redis集群可以保证分布式锁的高可用;
    3. 基于zookeeper实现分布式锁:基于zookeeper的临时有序节点可以实现分布式锁,每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点目录下,生成一个唯一的临时有序节点。获取锁时,只需要判断有序节点中序号最小的一个。

 

   

12. 设计模式

  • 设计模式的六大原则

  1. 开闭原则:对扩展开放、对修改关闭。即程序扩展时不能去修改原有代码,为实现这种效果尽量实现接口和抽象类

  2. 里氏替换原则(lsp):里氏替换原则是对实现抽象化的具体步骤的规范

  3. 依赖倒转原则:针对接口变成,依赖于抽象,而不依赖与具体

  4. 接口隔离原则:使用多个隔离接口,比使用单个接口要好,降低类之间的耦合度

  5. 最少知道法则(迪米特法则):一个实体应当尽量少地与其他实体发生相互作用,使系统功能模块相对独立

  6. 合成复用原则:尽量使用合成/聚合的方式(持有对方对象),而不是继承

  • 设计模式分类

  1. 创建者模式:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式

  2. 结构型模式:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式

  3. 行为模式:策略模式、状态模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、访问模式、中介者模式、解释器模式

  • 常用设计模式

  • 代理模式通过代理对象访问目标对象,这样可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法 。   
  1. 静态代理:代理类和委托类实现相同的接口,代理类中接收并保存代理类对象,实现方法中调用委托类方法。

  2. JDK代理(动态代理):代理对象不需要实现接口,代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象,但目标对象必须实现接口。 

  3. CGLIB代理:也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。目标对象可以不用实现接口。       

  • 单例模式:单例类只能有一个实例,必须自己创建自己的唯一实例,并给其他对象提供这一实例。单例模式分为懒汉模式、饿汉模式和注册表模式。

      1. 懒汉模式:私有化构造器,提供获取实例静态方法,在其他类调用该方法时实例化,懒汉模式是非同步,在多线程下需要做同步处理,也可以通过静态内部类来实现线程安全的懒汉模式单例;  
      2. 饿汉模式:也是私有化构造器,提供获取实例静态方法,但饿汉模式是在初始化静态域时就实例化了自己,是线程安全的  
      3. 注册表模式: 保护的构造器,利用了classloader的机制来保证初始化instance时只有一个线程,是线程安全的,可以被继承 
  • 适配器模式:适配器将某个接口转换成客户端期望的另一个接口表示,主要目的是兼容性,即接口不匹配问题,source-》adapter-》target。适配器分以下三种类型:       

      1. 类适配器:将source当做一个类,继承,即适配器继承source类;
      2. 对象适配器:将source当成一个对象,持有,即适配器不再继承source类,而是持有source的对象;
      3. 接口适配器:将source做为一个接口,实现。
  • 观察者模式:在对象之间定义了一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象会收到通知并自动更新。这个模式是松耦合的,改变主题或观察者中的一方,另一方不会受到。

      1. 抽象被观察者:也就是抽象主题,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及通知观察者更新数据的方法;
      2. 抽象观察者:为所有的具体观察者定义一个接口,该接口规定了一个在得到主题通知时更新自己的方法;
      3. 具体被观察者:也就是具体主题,实现抽象主题的方法,并提供一个list存放添加的观察者;
      4. 具体观察者:实现抽象观察者;
  • 状态机模式:允许对象在内部转态改变时,改变它的行为,对象看起来就好像修改了它的类(每个状态可以做出不同的动作)。

状态机组成:

      • State状态机接口:定义了使得状态机发生状态转换的方法;
      • 具体状态类:每一个状态类都是状态机的一种状态,实现了State状态机接口,持有状态机对象,在执行状态转换方法后改变状态机的状态;
      • 状态机类:持有不同状态类对象,并有一个表示当前状态的属性,并定义客户感兴趣的接口
  • 策略模式:策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们可以相互替换,使算法独立于使用它们的客户而独立变化

      1. 抽象策略角色:通常是抽象类或接口,给出具体策略角色类所需的接口方法
      2. 具体策略角色:实现抽象策略类,实现相关业务
      3. 环境角色:持有一个strategy(策略角色)引用,通过他实现不同具体策略角色的切换
  • 桥接模式:将抽象部分与它的实现部分分离开来,使它们可以独立变化。提高了系统的可扩充性,实现细节对客户透明,可以对客户隐藏实现细节,jdbc就是使用的桥接模式。包含如下几个角色:

      1. 抽象类:持有一个实现类接口对象    
      2. 扩充抽象类: 继承抽象类,通过实现类接口对象的多态性调用不同的具体实现类方法
      3. 实现类接口:定义具体行为的方法
      4. 具体实现类:实现具体行为            
  •         所有设计模式代码实现详见码云:设计模式实现  

  

    

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(java基础,面试,java,面试,基础)