2 Star 2 Fork 1

shangxun / java面试指南

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
Apache-2.0

java面试指南

介绍

分享一些有关于 Java 体系的知识,包括Java 基础知识/数据结构/算法/面试技巧,高并发/高性能/分布式/微服务架构的原理,内容涵盖Java面试指南、Spring Boot、Dubbo、Zookeeper、Redis、Nginx、消息队列、系统设计、架构、编程规范等。

java面试指南

Java 基础

1,JDK和JRE有什么区别?
JRE:Java Runtime Environment( java 运行时环境)。即java程序的运行时环境,包含了 java 虚拟机,java基础类库。
JDK:Java Development Kit( java 开发工具包)。即java语言编写的程序所需的开发工具包。JDK 包含了 JRE,同时还包括 java 源码的编译器 javac、监控工具 jconsole、分析工具 jvisualvm等。
2,==和equals的区别是什么?
== 是关系运算符,equals() 是方法,结果都返回布尔值
Object 的 == 和 equals() 比较的都是地址,作用相同
 
== 作用:
基本类型,比较值是否相等
引用类型,比较内存地址值是否相等
不能比较没有父子关系的两个对象

equals()方法的作用:
JDK 中的类一般已经重写了 equals(),比较的是内容
自定义类如果没有重写 equals(),将调用父类(默认 Object 类)的 equals() 方法,Object 的 equals() 比较使用了 this == obj
可以按照需求逻辑,重写对象的 equals() 方法(重写 equals 方法,一般须重写 hashCode 方法)
3,==和equals的区别是什么?
不属于。
Java 中 8 种基础的数据类型:byte、short、char、int、long、float、double、boolean
但是 String 类型却是最常用到的引用类型。   
4,普通类和抽象类有哪些区别?
抽象类不能被实例化
抽象类可以有抽象方法,抽象方法只需申明,无需实现
含有抽象方法的类必须申明为抽象类
抽象类的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类
抽象方法不能被声明为静态
抽象方法不能用 private 修饰
抽象方法不能用 final 修饰
5,JDK、JRE、JVM之间的关系是什么样的?
JDK 是 JAVA 程序开发时用的开发工具包,包含 Java 运行环境 JRE
JDk、JRE 内部都包含 JAVA虚拟机 JVM
JVM 包含 Java 应用程序的类的解释器和类加载器等
6,讲讲面向对象三大特征
封装
封装:就是隐藏对象的属性和实现细节,仅对外提供公共访问方式。让使用者知道的才暴露出来,不需要让使用者知道的全部隐藏起来
封装的好处:避免使用者直接操作属性值,隐藏类的实现细节;让使用者只能通过程序员规定的方法来访问数据;可以方便的加入存取控制语句,限制不合理操作,提高程序安全性。

继承
继承是类与类之间的关系,与现实世界中的继承(例如孩子继承父母基因)类似。
继承可以理解为一个类从另一个类获取方法和属性的过程。
例如:类B继承于类A,那么B就拥有A的部分方法和属性(private修饰或不在同一个包default修饰的无法访问)。

3.多态
多态是同一个行为具有多个不同表现形式或形态的能力。
多态分类和实现
编译时多态(静态多态):方法重载实现的多态
运行时多态(动态多态):类继承/接口实现 + 重写 + 向上转型实现的多态
基于继承/接口来实现的多态:面向接口的方式编程为的就是可以使用多态的特性让编程变得更加灵活。
多态的优点:消除类型之间的耦合关系(可替换性,可扩充性,接口性,灵活性,简化性)
7,什么是泛型?为什么要使用泛型?
泛型:
"参数化类型",将类型由具体的类型参数化,把类型也定义成参数形式(称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
是 JDK 5 中引入的一个新特性,提供了编译时类型安全监测机制,该机制允许程序员在编译时监测非法的类型。
泛型的本质是把参数的类型参数化,也就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中。

为什么要用泛型?
使用泛型编写的程序代码,要比使用 Object 变量再进行强制类型转换的代码,具有更好的安全性和可读性。
多种数据类型执行相同的代码使用泛型可以复用代码。
比如集合类使用泛型,取出和操作元素时无需进行类型转换,避免出现 java.lang.ClassCastException 异常
8,String、StringBuilder、StringBuffer的区别?
相同点:
都可以储存和操作字符串
都使用 final 修饰,不能被继承
提供的 API 相似

区别:
String 是只读字符串,String 对象内容是不能被改变的
StringBuffer 和 StringBuilder 的字符串对象可以对字符串内容进行修改,在修改后的内存地址不会发生改变
StringBuilder 线程不安全;StringBuffer 线程安全
方法体内没有对字符串的并发操作,且存在大量字符串拼接操作,建议使用 StringBuilder,效率较高。
9,&和&&的作用和区别
&
逻辑与,& 两边的表达式都会进行运算
整数的位运算符
 
&&
短路与,&& 左边的表达式结果为 false 时,&& 右边的表达式不参与计算
10,说一说你的对面向过程和面向对象的理解
  软件开发思想,先有面向过程,后有面向对象
  在大型软件系统中,面向过程的做法不足,从而推出了面向对象
  都是解决实际问题的思维方式
  两者相辅相成,宏观上面向对象把握复杂事物的关系;微观上面向过程去处理
  面向过程以实现功能的函数开发为主;面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能
  面向过程是封装的是功能;面向对象封装的是数据和功能
  面向对象具有继承性和多态性;面向过程则没有
11,说一说你的对面向过程和面向对象的理解
垃圾回收机制,简称 GC
Java 语言不需要程序员直接控制内存回收,由 JVM 在后台自动回收不再使用的内存
提高编程效率
保护程序的完整性
JVM 需要跟踪程序中有用的对象,确定哪些是无用的,影响性能

特点
回收 JVM 堆内存里的对象空间,不负责回收栈内存数据
无法处理一些操作系统资源的释放,如数据库连接、输入流输出流、Socket 连接
垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行
可以将对象的引用变量设置为 null,垃圾回收机制可以在下次执行时回收该对象。
JVM 有多种垃圾回收 实现算法,表现各异
垃圾回收机制回收任何对象之前,会先调用对象的 finalize() 方法
可以通过 System.gc() 或 Runtime.getRuntime().gc() 通知系统进行垃圾回收,会有一些效果,但系统是否进行垃圾回收依然不确定
不要主动调用对象的 finalize() 方法,应该交给垃圾回收机制调用
12,说一说你的对面向过程和面向对象的理解
内存溢出(out of memory):指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory。
内存泄露(memory leak):指程序在申请内存后,无法释放已申请的内存空间,内存泄露堆积会导致内存被占光。
memory leak 最终会导致 out of memory。
13,说一说你的对面向过程和面向对象的理解
final 表示最终的、不可改变的。用于修饰类、方法和变量。final 修饰的类不能被继承;final 方法也同样只能使用,不能重写,但能够重载;final 修饰的成员变量必须在声明时给定初值或者在构造方法内设置初始值,只能读取,不可修改;final 修饰的局部变量必须在声明时给定初值;final 修饰的变量是非基本类型,对象的引用地址不能变,但对象的属性值可以改变
finally 异常处理的一部分,它只能用在 try/catch 语句中,表示希望 finally 语句块中的代码最后一定被执行(存在一些情况导致 finally 语句块不会被执行,如 jvm 结束)
finalize() 是在 java.lang.Object 里定义的,Object 的 finalize() 方法什么都不做,对象被回收时 finalize() 方法会被调用。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要清理工作,在垃圾收集器删除对象之前被调用的。一般情况下,此方法由JVM调用。特殊情况下,可重写 finalize() 方法,当对象被回收的时候释放一些资源,须调用 super.finalize() 。 
14,return与finally的执行顺序对返回值的影响
对于 try 和 finally 至少一个语句块包含 return 语句的情况:
finally 语句块会执行
finally 没有 return,finally 对 return 变量的重新赋值修改无效
try 和 finally 都包含 return,return 值会以 finally 语句块 return 值为准
如下面的例子

如下面的例子

    public static void main(String[] args) {
        System.out.println(getString());
    }
    	
    public static String getString() {
        String str = "A";
        try {
            str = "B";
            return str;
        } finally {
            System.out.println("finally change return string to C");
            str = "C";
        }
    }

打印

    finally change return string to C
    B

finally 语句块中新增 return 语句

     public static void main(String[] args) {
         System.out.println(getString());
     }
     
     public static String getString() {
         String str = "A";
         try {
             str = "B";
             return str;
         } finally {
             System.out.println("finally change return string to C");
             str = "C";
             return str;
         }
     }

打印结果

    finally change return string to C
    C
15,Math.round(-1.5) 等于多少?
运行结果: -1

JDK 中的 java.lang.Math 类
ceil() :向上取整,返回小数所在两整数间的较大值,返回类型是 double,如 -1.5 返回 -1.0
floor() :向下取整,返回小数所在两整数间的较小值,返回类型是 double,如 -1.5 返回 -2.0
round() :朝正无穷大方向返回参数最接近的整数,可以换算为 参数 + 0.5 向下取整,返回值是 int 或 long,如 -1.5 返回 -1
16,抽象类能使用final修饰吗?
不能,抽象类是被用于继承的,final修饰代表不可修改、不可继承的。
17,抽象类能使用final修饰吗?
throw:
表示方法内抛出某种异常对象(只能是一个)
用于程序员自行产生并抛出异常
位于方法体内部,可以作为单独语句使用
如果异常对象是非 RuntimeException 则需要在方法申明时加上该异常的抛出,即需要加上 throws 语句 或者 在方法体内 try catch 处理该异常,否则编译报错
执行到 throw 语句则后面的语句块不再执行

throws:
方法的定义上使用 throws 表示这个方法可能抛出某些异常(可以有多个)
用于声明在该方法内抛出了异常
必须跟在方法参数列表的后面,不能单独使用
需要由方法的调用者进行异常处理
18,动态代理是什么?应用场景?
动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法。
Java 中实现动态的方式:
JDK 中的动态代理 
Java类库 CGLib

应用场景:
统计每个 api 的请求耗时
统一的日志输出
校验被调用的 api 是否已经登录和权限鉴定
Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程
19,动态代理是什么?应用场景?
final 语义是不可改变的。
被 final 修饰的类,不能够被继承
被 final 修饰的成员变量必须要初始化,赋初值后不能再重新赋值(可以调用对象方法修改属性值)。对基本类型来说是其值不可变;对引用变量来说其引用不可变,即不能再指向其他的对象
被 final 修饰的方法不能重写
20,finally语句块一定执行吗?
答案是不一定。存在很多特殊情况导致 finally 语句块不执行。如:
直接返回未执行到 try-finally 语句块
抛出异常未执行到 try-finally 语句块
系统退出未执行到 finally 语句块
21,String属于基础的数据类型吗?
不属于。
Java 中 8 种基础的数据类型:byte、short、char、int、long、float、double、boolean
但是 String 类型却是最常用到的引用类型。
22,面向对象和面向过程的区别?
面向过程:
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展。

面向对象:
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
缺点:性能比面向过程低。
23,面向对象和面向过程的区别?
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重写发生在子类与父类之间, 重写方法返回值和形参都不能改变,与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分。即外壳不变,核心重写!
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。
24,抽象类和接口的区别是什么?
语法层面上的区别:
抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
一个类只能继承一个抽象类,而一个类却可以实现多个接口。

设计层面上的区别:
抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。
25,java 创建对象有哪几种方式?
java中提供了以下四种创建对象的方式:
new创建新对象
通过反射机制
采用clone机制
通过序列化机制
前两者都需要显式地调用构造方法。对于clone机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在java中序列化可以通过实现Externalizable或者Serializable来实现。
26,为什么重写 equals 方法必须重写 hashcode 方法 ?
判断的时候先根据hashcode进行的判断,相同的情况下再根据equals()方法进行判断。如果只重写了equals方法,而不重写hashcode的方法,会造成hashcode的值不同,而equals()方法判断出来的结果为true。
在Java中的一些容器中,不允许有两个完全相同的对象,插入的时候,如果判断相同则会进行覆盖。这时候如果只重写了equals()的方法,而不重写hashcode的方法,Object中hashcode是根据对象的存储地址转换而形成的一个哈希值。这时候就有可能因为没有重写hashcode方法,造成相同的对象散列到不同的位置而造成对象的不能覆盖的问题。
27,包装类型是什么?基本类型和包装类型有什么区别?
Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,把基本类型转换成包装类型的过程叫做装箱(boxing);反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing),使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
原始类型: boolean,char,byte,short,int,long,float,double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

基本类型和包装类型的区别主要有以下 几点:
包装类型可以为 null,而基本类型不可以。它使得包装类型可以应用于 POJO 中,而基本类型则不行。那为什么 POJO 的属性必须要用包装类型呢?《阿里巴巴 Java 开发手册》上有详细的说明, 数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值),就会抛出 NullPointerException 的异常。
包装类型可用于泛型,而基本类型不可以。泛型不能使用基本类型,因为使用基本类型时会编译出错。
List<int> list = new ArrayList<>(); // 提示 Syntax error, insert "Dimensions" to complete ReferenceType
List<Integer> list = new ArrayList<>();
因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类——基本类型是个特例。
基本类型比包装类型更高效。基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。 很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间。
28,int 和 Integer 有什么区别?
Integer是int的包装类;int是基本数据类型;
Integer变量必须实例化后才能使用;int变量不需要;
Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
Integer的默认值是null;int的默认值是0。    
29,什么是反射?反射机制的优缺点有哪些?
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
优点:能够运行时动态获取类的实例,提高灵活性;可与动态编译结合Class.forName('com.mysql.jdbc.Driver.class');,加载MySQL的驱动类。
缺点:使用反射性能较低,需要解析字节码,将内存中的对象进行解析。其解决方案是:通过setAccessible(true)关闭JDK的安全检查来提升反射速度;多次创建一个类的实例时,有缓存会快很多;ReflflectASM工具类,通过字节码生成的方式加快反射速度。
30,Java反射API有几类?
反射 API 用来生成 JVM 中的类、接口或则对象的信息。
Class 类:反射的核心类,可以获取类的属性,方法等信息。
Field 类:Java.lang.reflec 包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
Method 类:Java.lang.reflec 包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
Constructor 类:Java.lang.reflec 包中的类,表示类的构造方法。
31,Java序列化与反序列化是什么?
Java序列化是指把Java对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程:
序列化:序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。核心作用是对象状态的保存与重建。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。
而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。
反序列化:客户端从文件中或网络上获得序列化后的对象字节流,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
32,为什么需要序列化与反序列化?
简要描述:对内存中的对象进行持久化或网络传输, 这个时候都需要序列化和反序列化
深入描述:
对象序列化可以实现分布式对象。
主要应用例如:RMI(即远程调用Remote Method Invocation)要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。
可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列。
序列化可以将内存中的类写入文件或数据库中。
比如:将某个类序列化后存为文件,下次读取时只需将文件中的数据反序列化就可以将原先的类还原到内存中。也可以将类序列化为流数据进行传输。
总的来说就是将一个已经实例化的类转成文件存储,下次需要实例化的时候只要反序列化即可将类实例化到内存中并保留序列化时类中的所有变量和状态。
对象、文件、数据,有许多不同的格式,很难统一传输和保存。
序列化以后就都是字节流了,无论原来是什么东西,都能变成一样的东西,就可以进行通用的格式传输或保存,传输结束以后,要再次使用,就进行反序列化还原,这样对象还是对象,文件还是文件。
33,为什么需要序列化与反序列化?
java.lang.IllegalAccessError:违法访问错误。当一个应用试图访问、修改某个类的域(Field)或者调用其方法,但是又违反域或方法的可见性声明,则抛出该异常。
java.lang.InstantiationError:实例化错误。当一个应用试图通过Java的new操作符构造一个抽象类或者接口时抛出该异常.
java.lang.OutOfMemoryError:内存不足错误。当可用内存不足以让Java虚拟机分配给一个对象时抛出该错误。
java.lang.StackOverflowError:堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出或者陷入死循环时抛出该错误。
java.lang.ClassCastException:类造型异常。假设有类A和B(A不是B的父类或子类),O是A的实例,那么当强制将O构造为类B的实例时抛出该异常。该异常经常被称为强制类型转换异常。
java.lang.ClassNotFoundException:找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常。
java.lang.ArithmeticException:算术条件异常。譬如:整数除零等。
java.lang.ArrayIndexOutOfBoundsException:数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出。
java.lang.IndexOutOfBoundsException:索引越界异常。当访问某个序列的索引值小于0或大于等于序列大小时,抛出该异常。
java.lang.InstantiationException:实例化异常。当试图通过newInstance()方法创建某个类的实例,而该类是一个抽象类或接口时,抛出该异常。
java.lang.NoSuchFieldException:属性不存在异常。当访问某个类的不存在的属性时抛出该异常。
java.lang.NoSuchMethodException:方法不存在异常。当访问某个类的不存在的方法时抛出该异常。
java.lang.NullPointerException:空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等。
java.lang.NumberFormatException:数字格式异常。当试图将一个String转换为指定的数字类型,而该字符串确不满足数字类型要求的格式时,抛出该异常。
java.lang.StringIndexOutOfBoundsException:字符串索引越界异常。当使用索引值访问某个字符串中的字符,而该索引值小于0或大于等于序列大小时,抛出该异常。
34,try-catch-finally 中哪个部分可以省略?
catch 可以省略。更为严格的说法其实是:try只适合处理运行时异常,try+catch适合处理运行时异常+普通异常。也就是说,如果你只用try去处理普通异常却不加以catch处理,编译是通不过的,因为编译器硬性规定,普通异常如果选择捕获,则必须用catch显示声明以便进一步处理。而运行时异常在编译时没有如此规定,所以catch可以省略,你加上catch编译器也觉得无可厚非。
理论上,编译器看任何代码都不顺眼,都觉得可能有潜在的问题,所以你即使对所有代码加上try,代码在运行期时也只不过是在正常运行的基础上加一层皮。但是你一旦对一段代码加上try,就等于显示地承诺编译器,对这段代码可能抛出的异常进行捕获而非向上抛出处理。如果是普通异常,编译器要求必须用catch捕获以便进一步处理;如果运行时异常,捕获然后丢弃并且+finally扫尾处理,或者加上catch捕获以便进一步处理。
至于加上finally,则是在不管有没捕获异常,都要进行的“扫尾”处理。
35,JVM 是如何处理异常的?
在一个方法中如果发生异常,这个方法会创建一个异常对象,并转交给 JVM,该异常对象包含异常名称,异常描述以及异常发生时应用程序的状态。创建异常对象并转交给 JVM 的过程称为抛出异常。可能有一系列的方法调用,最终才进入抛出异常的方法,这一系列方法调用的有序列表叫做调用栈。
JVM 会顺着调用栈去查找看是否有可以处理异常的代码,如果有,则调用异常处理代码。当 JVM 发现可以处理异常的代码时,会把发生的异常传递给它。如果 JVM 没有找到可以处理该异常的代码块,JVM 就会将该异常转交给默认的异常处理器(默认处理器为 JVM 的一部分),默认异常处理器打印出异常信息并终止应用程序。    
36,Java的IO 流分为几种?
按照流的方向:输入流(inputStream)和输出流(outputStream);
按照实现功能分:节点流(可以从或向一个特定的地方读写数据,如 FileReader)和处理流(是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写, BufferedReader);
按照处理数据的单位: 字节流和字符流。分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的。
37,字节流如何转为字符流?
字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。
38,字符流与字节流的区别?
读写的时候字节流是按字节读写,字符流按字符读写。
字节流适合所有类型文件的数据传输,因为计算机字节(Byte)是电脑中表示信息含义的最小单位。字符流只能够处理纯文本数据,其他类型数据不行,但是字符流处理文本要比字节流处理文本要方便。
在读写文件需要对内容按行处理,比如比较特定字符,处理某一行数据的时候一般会选择字符流。
只是读写文件,和文件内容无关时,一般选择字节流。
39,BIO、NIO、AIO的区别?
BIO:同步并阻塞,在服务器中实现的模式为一个连接一个线程。也就是说,客户端有连接请求的时候,服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然这也可以通过线程池机制改善。BIO一般适用于连接数目小且固定的架构,这种方式对于服务器资源要求比较高,而且并发局限于应用中,是JDK1.4之前的唯一选择,但好在程序直观简单,易理解。
NIO:同步并非阻塞,在服务器中实现的模式为一个请求一个线程,也就是说,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到有连接IO请求时才会启动一个线程进行处理。NIO一般适用于连接数目多且连接比较短(轻操作)的架构,并发局限于应用中,编程比较复杂,从JDK1.4开始支持。
AIO:异步并非阻塞,在服务器中实现的模式为一个有效请求一个线程,也就是说,客户端的IO请求都是通过操作系统先完成之后,再通知服务器应用去启动线程进行处理。AIO一般适用于连接数目多且连接比较长(重操作)的架构,充分调用操作系统参与并发操作,编程比较复杂,从JDK1.7开始支持。

集合

1,常见的集合有哪些?
Java集合类主要由两个根接口Collection和Map派生出来的,Collection派生出了三个子接口:List、Set、Queue(Java5新增的队列),因此Java集合大致也可分成List、Set、Queue、Map四种接口体系。
注意:Collection是一个接口,Collections是一个工具类,Map不是Collection的子接口。

Java集合框架图如下: 集合架构图

2,线程安全的集合有哪些?线程不安全的呢?
线程安全的:
Hashtable:比HashMap多了个线程安全。
ConcurrentHashMap:是一种高效但是线程安全的集合。
Vector:比Arraylist多了个同步化机制。
Stack:栈,也是线程安全的,继承于Vector。

线性不安全的:
HashMap
Arraylist
LinkedList
HashSet
TreeSet
TreeMap
3,ArrayList 与 Vector 区别?
Vector是线程安全的,ArrayList不是线程安全的。其中,Vector在关键性的方法前面都加了synchronized关键字,来保证线程的安全性。如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍,这样ArrayList就有利于节约内存空间。    
4,Arraylist与 LinkedList 异同点?
是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向循环链表数据结构;
插入和删除是否受元素位置的影响: ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element))时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。
是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而ArrayList 实现了RandmoAccess 接口,所以有随机访问功能。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)方法)。
内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
5,说一说ArrayList 的扩容机制?
ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。默认情况下,新的容量会是原容量的1.5倍。

以JDK1.8为例说明:

public boolean add(E e) {
    //判断是否可以容纳e,若能,则直接添加在末尾;若不能,则进行扩容,然后再把e添加在末尾
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //将e添加到数组末尾
    elementData[size++] = e;
    return true;
    }

// 每次在add()一个元素时,arraylist都需要对这个list的容量进行一个判断。通过ensureCapacityInternal()方法确保当前ArrayList维护的数组具有存储新元素的能力,经过处理之后将元素存储在数组elementData的尾部

private void ensureCapacityInternal(int minCapacity) {
      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    
  private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // 若ArrayList已有的存储能力满足最低存储要求,则返回add直接添加元素;如果最低要求的存储能力>ArrayList已有的存储能力,这就表示ArrayList的存储能力不足,因此需要调用 grow();方法进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }


private void grow(int minCapacity) {
        // 获取elementData数组的内存空间长度
        int oldCapacity = elementData.length;
        // 扩容至原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //校验容量是否够
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //若预设值大于默认的最大值,检查是否溢出
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 调用Arrays.copyOf方法将elementData数组指向新的内存空间
         //并将elementData的数据复制到新的内存空间
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
6,Array 和 ArrayList 有什么区别?
Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
Array 大小是固定的,ArrayList 的大小是动态变化的。
ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。
7,HashMap的底层数据结构是什么?
在JDK1.7 和JDK1.8 中有所差别:
在JDK1.7 中,由“数组+链表”组成,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。
在JDK1.8 中,由“数组+链表+红黑树”组成。当链表过长,则会严重影响 HashMap 的性能,红黑树搜索时间复杂度是 O(logn),而链表是糟糕的 O(n)。因此,JDK1.8 对数据结构做了进一步的优化,引入了红黑树,链表和红黑树在达到一定条件会进行转换:
当链表超过 8 且数据总量超过 64 才会转红黑树。
将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树,以减少搜索时间。

MashMap

8,解决hash冲突的办法有哪些?HashMap用的哪种?
解决Hash冲突方法有:开放定址法、再哈希法、链地址法(拉链法)、建立公共溢出区。HashMap中采用的是 链地址法 。
开放定址法也称为再散列法,基本思想就是,如果p=H(key)出现冲突时,则以p为基础,再次hash,p1=H(p),如果p1再次出现冲突,则以p1为基础,以此类推,直到找到一个不冲突的哈希地址pi。 因此开放定址法所需要的hash表的长度要大于等于所需要存放的元素,而且因为存在再次hash,所以只能在删除的节点上做标记,而不能真正删除节点。
再哈希法(双重散列,多重散列),提供多个不同的hash函数,当R1=H1(key1)发生冲突时,再计算R2=H2(key1),直到没有冲突为止。 这样做虽然不易产生堆集,但增加了计算的时间。
链地址法(拉链法),将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中,查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。
建立公共溢出区,将哈希表分为公共表和溢出表,当溢出发生时,将所有溢出数据统一放到溢出区。
9,解决hash冲突的办法有哪些?HashMap用的哪种?
因为红黑树需要进行左旋,右旋,变色这些操作来保持平衡,而单链表不需要。当元素小于 8 个的时候,此时做查询操作,链表结构已经能保证查询性能。当元素大于 8 个的时候, 红黑树搜索时间复杂度是 O(logn),而链表是 O(n),此时需要红黑树来加快查询速度,但是新增节点的效率变慢了。
因此,如果一开始就用红黑树结构,元素太少,新增效率又比较慢,无疑这是浪费性能的。
10,HashMap默认加载因子是多少?为什么是 0.75,不是 0.6 或者 0.8 ?
回答这个问题前,我们来先看下HashMap的默认构造函数:
int threshold;             // 容纳键值对的最大值
final float loadFactor;    // 负载因子
int modCount;  
int size;  

Node[] table的初始化长度length(默认值是16),Load factor为负载因子(默认值是0.75),threshold是HashMap所能容纳键值对的最大值。threshold = length * Load factor。也就是说,在数组定义好长度之后,负载因子越大,所能容纳的键值对个数越多。
默认的loadFactor是0.75,0.75是对空间和时间效率的一个平衡选择,一般不要修改,除非在时间和空间比较特殊的情况下 :
如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值 。
相反,如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。
我们来追溯下作者在源码中的注释(JDK1.7):
As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put). The expected number of entries in the map and its load factor should be taken into account when setting its initial capacity, so as to minimize the number of rehash operations. If the initial capacity is greater than the maximum number of entries divided by the load factor, no rehash operations will ever occur.
翻译过来大概的意思是:作为一般规则,默认负载因子(0.75)在时间和空间成本上提供了很好的折衷。较高的值会降低空间开销,但提高查找成本(体现在大多数的HashMap类的操作,包括get和put)。设置初始大小时,应该考虑预计的entry数在map及其负载系数,并且尽量减少rehash操作的次数。如果初始容量大于最大条目数除以负载因子,rehash操作将不会发生。
11,HashMap为什么线程不安全?
多线程下扩容死循环。JDK1.7中的 HashMap 使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。因此,JDK1.8使用尾插法插入元素,在扩容时会保持链表元素原本的顺序,不会出现环形链表的问题。
多线程的put可能导致元素的丢失。多线程同时执行 put 操作,如果计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致元素的丢失。此问题在JDK 1.7和 JDK 1.8 中都存在。
put和get并发时,可能导致get为null。线程1执行put时,因为元素个数超出threshold而导致rehash,线程2此时执行get,有可能导致这个问题。此问题在JDK 1.7和 JDK 1.8 中都存在。

MashMap

12,ConcurrentHashMap 的实现原理是什么?
ConcurrentHashMap 在 JDK1.7 和 JDK1.8 的实现方式是不同的。
JDK1.7
JDK1.7中的ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成,即ConcurrentHashMap 把哈希桶切分成小数组(Segment ),每个小数组有 n 个 HashEntry 组成。
其中,Segment 继承了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色;HashEntry 用于存储键值对数据。

MashMap

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问,能够实现真正的并发访问。

JDK1.8
在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用CAS + synchronized实现更加低粒度的锁。
将锁的级别控制在了更细粒度的哈希桶元素级别,也就是说只需要锁住这个链表头结点(红黑树的根节点),就不会影响其他的哈希桶元素的读写,大大提高了并发度。

MashMap

13,ConcurrentHashMap 的 get 方法是否要加锁,为什么?
get 方法不需要加锁。因为 Node 的元素 val 和指针 next 是用 volatile 修饰的,在多线程环境下线程A修改结点的val或者新增节点的时候是对线程B可见的。
这也是它比其他并发集合比如 Hashtable、用 Collections.synchronizedMap()包装的 HashMap 安全效率高的原因之一。
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    //可以看到这些都用了volatile修饰
    volatile V val;
    volatile Node<K,V> next;
}       
14,JDK1.7与JDK1.8 中ConcurrentHashMap 的区别?
数据结构:取消了Segment分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
保证线程安全机制:JDK1.7采用Segment的分段锁机制实现线程安全,其中segment继承自ReentrantLock。JDK1.8 采用CAS+Synchronized保证线程安全。
锁的粒度:原来是对需要进行数据操作的Segment加锁,现调整为对每个数组元素加锁(Node)。
链表转化为红黑树:定位结点的hash算法简化会带来弊端,Hash冲突加剧,因此在链表节点数量大于8时,会将链表转化为红黑树进行存储。
查询时间复杂度:从原来的遍历链表O(n),变成遍历红黑树O(logN)。
15,ConcurrentHashMap 和Hashtable的效率哪个更高?为什么?
ConcurrentHashMap 的效率要高于Hashtable,因为Hashtable给整个哈希表加了一把大锁从而实现线程安全。而ConcurrentHashMap 的锁粒度更低,在JDK1.7中采用分段锁实现线程安全,在JDK1.8 中采用CAS+Synchronized实现线程安全。
16,说一下Hashtable的锁机制 ?
Hashtable是使用Synchronized来实现线程安全的,给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞等待需要的锁被释放,在竞争激烈的多线程场景中性能就会非常差!

MashMap

17,Collection和Collections有什么区别?
Collection 是JDK中集合层次结构中的最根本的接口。定义了集合类的基本方法。
源码中的解释:
* The root interface in the <i>collection hierarchy</i>.  A collection
* represents a group of objects, known as its <i>elements</i>.  Some
* collections allow duplicate elements and others do not.  Some are ordered
* and others unordered.  The JDK does not provide any <i>direct</i>
* implementations of this interface: it provides implementations of more
* specific subinterfaces like <tt>Set</tt> and <tt>List</tt>.  This interface
* is typically used to pass collections around and manipulate them where
* maximum generality is desired.
       
Collections 是一个包装类。它包含有各种有关集合操作的静态多态方法,不能实例化,Collection 集合框架的工具类。 
源码中的解释:
* This class consists exclusively of static methods that operate on or return
* collections.  It contains polymorphic algorithms that operate on
* collections, "wrappers", which return a new collection backed by a
* specified collection, and a few other odds and ends.       
18,List、Set、Map 之间的区别是什么?
List:有序集合,元素可重复
Set:不重复集合,LinkedHashSet按照插入排序,SortedSet可排序,HashSet无序
Map:键值对集合,存储键、值和之间的映射;Key无序,唯一;value 不要求有序,允许重复
19,HashMap和Hashtable 有什么区别?
JDK 1.8 中 HashMap 和 Hashtable 主要区别如下:

线程安全性不同。HashMap 线程不安全;Hashtable 中的方法是 synchronized 的。
key、value 是否允许 null。HashMap 的 key 和 value 都是可以是 null,key 只允许一个 null;Hashtable 的 key 和 value 都不可为 null。
迭代器不同。HashMap 的 Iterator 是 fail-fast 迭代器;Hashtable 还使用了 enumerator 迭代器。
hash的计算方式不同。HashMap 计算了 hash值;Hashtable 使用了 key 的 hashCode方法。
默认初始大小和扩容方式不同。HashMap 默认初始大小 16,容量必须是 2 的整数次幂,扩容时将容量变为原来的2倍;Hashtable 默认初始大小 11,扩容时将容量变为原来的 2 倍加 1。
是否有 contains 方法。HashMap 没有 contains 方法;Hashtable 包含 contains 方法,类似于 containsValue。
父类不同。HashMap 继承自 AbstractMap;Hashtable 继承自 Dictionary。

参考https://www.cnblogs.com/williamjie/p/9099141.html

20,如何决定使用HashMap还是TreeMap?
HashMap基于散列桶(数组和链表)实现;TreeMap基于红黑树实现。
HashMap不支持排序;TreeMap默认是按照Key值升序排序的,可指定排序的比较器,主要用于存入元素时对元素进行自动排序。
HashMap大多数情况下有更好的性能,尤其是读数据。在没有排序要求的情况下,使用HashMap。
都是非线程安全。 
21,如何实现数组和List之间的转换?
数组转 List ,使用 JDK 中 java.util.Arrays 工具类的 asList 方法
public static void testArray2List() {
    String[] strs = new String[] {"aaa", "bbb", "ccc"};
    List<String> list = Arrays.asList(strs);
    for (String s : list) {
        System.out.println(s);
    }
}
List 转数组,使用 List 的 toArray 方法。无参 toArray 方法返回 Object 数组,传入初始化长度的数组对象,返回该对象数组     
public static void testList2Array() {
    List<String> list = Arrays.asList("aaa", "bbb", "ccc");
    String[] array = list.toArray(new String[list.size()]);
    for (String s : array) {
        System.out.println(s);
    }
}
22,Queue的add()和offer()方法有什么区别?
Queue 中 add() 和 offer() 都是用来向队列添加一个元素。
在容量已满的情况下,add() 方法会抛出IllegalStateException异常,offer() 方法只会返回 false 。
23,Queue的remove()和poll()方法有什么区别?
Queue 中 remove() 和 poll() 都是用来从队列头部删除一个元素。
在队列元素为空的情况下,remove() 方法会抛出NoSuchElementException异常,poll() 方法只会返回 null 。
24,Queue的remove()和poll()方法有什么区别?
Queue 中 element() 和 peek() 都是用来返回队列的头元素,不删除。
在队列元素为空的情况下,element() 方法会抛出NoSuchElementException异常,peek() 方法只会返回 null。
25,迭代器Iterator是什么?
首先说一下迭代器模式,它是 Java 中常用的设计模式之一。用于顺序访问集合对象的元素,无需知道集合对象的底层实现。
Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。
缺点是增加新的集合类需要对应增加新的迭代器类,迭代器类与集合类成对增加。
26,Iterator怎么使用?有什么特点?
java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象
next() 方法获得集合中的下一个元素
hasNext() 检查集合中是否还有元素
remove() 方法将迭代器新返回的元素删除
forEachRemaining(Consumer<? super E> action) 方法,遍历所有元素
27,Iterator和 ListIterator有什么区别?
ListIterator 继承 Iterator
ListIterator 比 Iterator多方法
1) add(E e)  将指定的元素插入列表,插入位置为迭代器当前位置之前
2) set(E e)  迭代器返回的最后一个元素替换参数e
3) hasPrevious()  迭代器当前位置,反向遍历集合是否含有元素
4) previous()  迭代器当前位置,反向遍历集合,下一个元素
5) previousIndex()  迭代器当前位置,反向遍历集合,返回下一个元素的下标
6) nextIndex()  迭代器当前位置,返回下一个元素的下标

使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类
ListIterator 有 add 方法,可以向 List 中添加对象;Iterator 不能
ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历;Iterator不可以
ListIterator 有 nextIndex() 和previousIndex() 方法,可定位当前索引的位置;Iterator不可以
ListIterator 有 set()方法,可以实现对 List 的修改;Iterator 仅能遍历,不能修改
28,怎么确保一个集合不能被修改?
使用 JDK中java.util.Collections 类,unmodifiable*** 方法赋值原集合。
当再修改集合时,会报错 java.lang.UnsupportedOperationException。从而确保自己定义的集合不被其他人修改。
29,为什么基本类型不能做为HashMap的键值?
Java中是使用泛型来约束 HashMap 中的key和value的类型的,HashMap<K, V>
泛型在Java的规定中必须是对象Object类型的,基本数据类型不是Object类型,不能作为键值
map.put(0, "ConstXiong")中编译器已将 key 值 0 进行了自动装箱,变为了 Integer 类型
30,为什么基本类型不能做为HashMap的键值?
数组的优点:
数组的效率高于集合类
数组能存放基本数据类型和对象;集合中只能放对象
 
数组的缺点:
不是面向对象的,存在明显的缺陷
数组长度固定且无法动态改变;集合类容量动态改变
数组无法判断其中实际存了多少元素,只能通过length属性获取数组的申明的长度
数组存储的特点是顺序的连续内存;集合的数据结构更丰富

JDK 提供集合的意义:
集合以类的形式存在,符合面向对象,通过简单的方法和属性调用可实现各种复杂操作
集合有多种数据结构,不同类型的集合可适用于不同场合
弥补了数组的一些缺点,比数组更灵活、实用,可提高开发效率
31,HashSet实现原理是什么?有什么特点?
HashSet 是基于 HashMap 实现的,查询速度特别快
HashMap 是支持 key 为 null 值的,所以 HashSet 支持添加 null 值
HashSet 存放自定义类时,自定义类需要重写 hashCode() 和 equals() 方法,确保集合对自定义类的对象的唯一性判断(具体判断逻辑,见 HashMap put() 方法,简单概括就是 key 进行 哈希。判断元素 hash 值是否相等、key 是否为同个对象、key 是否 equals。第 1 个条件为 true,2、3 有一个为 true,HashMap 即认为 key 相同)
无序、不可重复
32,HashSet和HashMap有什么区别?
HashMap
实现 Map 接口
键值对的方式存储
新增元素使用 put(K key, V value) 方法
底层通过对 key 进行 hash,使用数组 + 链表或红黑树对 key、value 存储
 
HashSet
实现 Set 接口
存储元素对象
新增元素使用 add(E e) 方法
底层是采用 HashMap 实现,大部分方法都是通过调用 HashMap 的方法来实现
33,ArrayList与LinkedList哪个插入性能高?
LinkedList 插入性能高
ArrayList 是基于数组实现的,添加元素时,存在扩容问题,扩容时需要复制数组,消耗性能
LinkedList 是基于链表实现的,只需要将元素添加到链表最后一个元素的下一个即可
34,LinkedHashMap、LinkedHashSet、LinkedList哪个最适合当作Stack使用?
LinkedList
分析:
Stack 是线性结构,具有先进后出的特点
LinkedList 天然支持 Stack 的特性,调用 push(E e) 方法放入元素,调用 pop() 方法取出栈顶元素,内部实现只需要移动指针即可
LinkedHashSet 是基于 LinkedHashMap 实现的,记录添加顺序的 Set 集合
LinkedHashMap 是基于 HashMap 和 链表实现的,记录添加顺序的键值对集合
如果要删除后进的元素,需要使用迭代器遍历、取出最后一个元素,移除,性能较差   
35,TreeMap和TreeSet在排序时如何比较元素?
TreeMap 会对 key 进行比较,有两种比较方式,第一种是构造方法指定 Comparator,使用 Comparator#compare() 方法进行比较;第二种是构造方法未指定 Comparator 接口,需要 key 对象的类实现 Comparable 接口,用 Comparable #compareTo() 方法进行比较
TreeSet 底层是使用 TreeMap 实现
36,List里如何剔除相同的对象?
package constxiong.interview;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 测试剔除List的相同元素
 * @author ConstXiong
 * @date 2019-11-06 16:33:17
 */
public class TestRemoveListSameElement {

    public static void main(String[] args) {
        List<String> l = Arrays.asList("1", "2", "3", "1");
        Set<String> s = new HashSet<String>(l);
        System.out.println(s);
    }

}
37,Java.util.Map的常用实现类有哪些?
HashMap、LinkedHashMap
Hashtable
TreeMap
IdentityHashMap
38,Java.util.Map的常用实现类有哪些?
List、Set 的父接口是 Collection
Map 不是其子接口,与 Collection 接口相互独立   
39,Java.util.Map的常用实现类有哪些?
ArrayList 和 Vector 都是使用数组存储数据
允许直接按序号索引元素
插入元素涉及数组扩容、元素移动等内存操作
根据下标找元素快,存在扩容的情况下插入慢
Vector 对元素的操作,使用了 synchronized 方法,性能比 ArrayList 差
Vector 属于遗留容器,早期的 JDK 中使用的容器
LinkedList 使用双向链表存储元素
LinkedList 按序号查找元素,需要进行前向或后向遍历,所以按下标查找元素,效率较低
LinkedList 非线程安全
LinkedList 使用的链式存储方式与数组的连续存储方式相比,对内存的利用率更高
LinkedList 插入数据时只需要移动指针即可,所以插入速度较快
40,说一下HashMap的实现原理
HashMap 基于 Hash 算法实现,通过 put(key,value) 存储,get(key) 来获取 value
当传入 key 时,HashMap 会根据 key,调用 hash(Object key) 方法,计算出 hash 值,根据 hash 值将 value 保存在 Node 对象里,Node 对象保存在数组里
当计算出的 hash 值相同时,称之为 hash 冲突,HashMap 的做法是用链表和红黑树存储相同 hash 值的 value
当 hash 冲突的个数:小于等于 8 使用链表;大于 8 且 tab length 大于等于 64 时,使用红黑树解决链表查询慢的问题
ps:
上述是 JDK 1.8 HashMap 的实现原理,并不是每个版本都相同,比如 JDK 1.7 的 HashMap 是基于数组 + 链表实现,所以 hash 冲突时链表的查询效率低
hash(Object key)  方法的具体算法是 (h = key.hashCode()) ^ (h >>> 16),经过这样的运算,让计算的 hash 值分布更均匀
41,说一下HashMap的实现原理
HashMap 是线程不安全的,效率高;HashTable 是线程安全的,效率低。
ConcurrentHashMap 可以做到既是线程安全的,同时也可以有很高的效率,得益于使用了分段锁。

实现原理
JDK 1.7:
ConcurrentHashMap 是通过数组 + 链表实现,由 Segment 数组和 Segment 元素里对应多个 HashEntry 组成
value 和链表都是 volatile 修饰,保证可见性
ConcurrentHashMap 采用了分段锁技术,分段指的就是 Segment 数组,其中 Segment 继承于 ReentrantLock
理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发,每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment

put 方法的逻辑较复杂:
尝试加锁,加锁失败 scanAndLockForPut 方法自旋,超过 MAX_SCAN_RETRIES 次数,改为阻塞锁获取
将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry
遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value
不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容
最后释放所获取当前 Segment 的锁
 
get 方法较简单:
将 key 通过 hash 之后定位到具体的 Segment,再通过一次 hash 定位到具体的元素上
由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了其内存可见性

JDK 1.8:
抛弃了原有的 Segment 分段锁,采用了 CAS + synchronized 来保证并发安全性
HashEntry 改为 Node,作用相同
val next 都用了 volatile 修饰
 
put 方法逻辑:
根据 key 计算出 hash 值
判断是否需要进行初始化
根据 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋
如果当前位置的 hashcode == MOVED == -1,则需要扩容
如果都不满足,则利用 synchronized 锁写入数据
如果数量大于 TREEIFY_THRESHOLD 则转换为红黑树

get 方法逻辑:
根据计算出来的 hash 值寻址,如果在桶上直接返回值
如果是红黑树,按照树的方式获取值
如果是链表,按链表的方式遍历获取值

JDK 1.7 到 JDK 1.8 中的 ConcurrentHashMap 最大的改动:
链表上的 Node 超过 8 个改为红黑树,查询复杂度 O(logn)
ReentrantLock 显示锁改为 synchronized,说明 JDK 1.8 中 synchronized 锁性能赶上或超过 ReentrantLock 

设计模式

1,什么是设计模式
设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
2,常用的设计模式有哪些?
创建型
工厂模式与抽象工厂模式 (Factory Pattern)(Abstract Factory Pattern)
单例模式 (Singleton Pattern)
建造者模式 (Builder Pattern)
原型模式 (Prototype Pattern)

结构型
适配器模式 (Adapter Pattern)
装饰器模式 (Decorator Pattern)
桥接模式 (Bridge Pattern)
外观模式 (Facade Pattern)
代理模式 (Proxy Pattern)
过滤器模式 (Filter、Criteria Pattern)
组合模式 (Composite Pattern)
享元模式 (Flyweight Pattern)

行为型
责任链模式(Chain of Responsibility Pattern)
观察者模式(Observer Pattern)
模板模式(Template Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
策略模式(Strategy Pattern)
状态模式(State Pattern)
备忘录模式(Memento Pattern)
空对象模式(Null Object Pattern)    
3,简单工厂和抽象工厂有什么区别?
简单工厂模式
是由一个工厂对象创建产品实例,简单工厂模式的工厂类一般是使用静态方法,通过不同的参数的创建不同的对象实例
可以生产结构中的任意产品,不能增加新的产品

抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无需制定他们具体的类,生产多个系列产品
生产不同产品族的全部产品,不能新增产品,可以新增产品族     
4,Spring 框架中都用到了哪些设计模式?

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

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

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

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

适配器模式

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

装饰器模式:

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

动态代理:

切面在应用运行的时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象创建动态的创建一个代理对象。SpringAOP就是以这种方式织入切面的。
织入:把切面应用到目标对象并创建新的代理对象的过程。

观察者模式:

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

策略模式:

Spring框架的资源访问Resource接口。该接口提供了更强的资源访问能力,Spring 框架本身大量使用了 Resource接口来访问底层资源。
5,JDK中几个常用的设计模式?

单例模式(Singleton pattern):

保证一个类仅有一个实例,并提供一个访问它的全局访问点,同时确保只有单个对象被创建;
java.lang.Runtime#getRuntime()
java.awt.Desktop#getDesktop()
java.lang.System#getSecurityManager()

工厂模式(Factory Pattern):

定义一个创建对象的接口,但由子类决定需要实例化哪一个类。工厂方法使得子类实例化的过程推迟;
主要解决接口选择的问题;
被用于各种不可变的类如 Boolean,像Boolean.valueOf;
对象实例创建的过程比较复杂,并且需要准备很多参数等等,比如说 spring的beanFactory;

观察者模式(Observer pattern):

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新;
java.util.Observer/ java.util.Observable(很少在现实世界中使⽤用)
所有实现java.util.EventListener(因此实际上各地的Swing)
javax.servlet.http.HttpSessionBindingListener
javax.servlet.http.HttpSessionAttributeListener
javax.faces.event.PhaseListener

装饰器模式(Decorator Pattern):

动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活;
为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀;
Java IO 到处都使用了装饰模式,典型例子就是 Buffered 系列类如BufferedReader和BufferedWriter,它们增强了Reader和Writer对象,以实现提升性能的 Buffer 层次的读取和写入;
项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

责任链模式(Chain of Responsibility Pattern):

避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止;【传递职责】
职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了;
java.util.logging.Logger#log()
javax.servlet.Filter#doFilter()

适配器模式(Adapter Pattern):

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作;
主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的;
Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller;

享元模式(Flyweight Pattern):

运用共享技术有效地支持大量细粒度的对象,享元模式通过共享对象来避免创建太多的对象;
在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建;
为了使用享元模式, 你需要确保你的对象是不可变的,这样你才能安全的共享。
JDK 中String 池、Integer 池以及Long 池都是很好的使用了享元模式的例子;

代理模式(Proxy Pattern):

为其他对象提供一种代理以控制对这个对象的访问;
在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层;
在AOP实现中用到了JDK的动态代理;

模板模式(Template Pattern):

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤;
一些方法通用,却在每一个子类都重新写了这一方法;
Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。一般情况下,我们都是使用继承的方式来实现模板模式,但是 Spring 并没有使用这种方式,而是使用Callback 模式与模板方法模式配合,既达到了代码复用的效果,同时增加了灵活性。
6,设计模式的六大原则
开放封闭原则(Open Close Principle)
里氏代换原则(Liskov Substitution Principle)
依赖倒转原则(Dependence Inversion Principle)
接口隔离原则(Interface Segregation Principle)
迪米特法则(最少知道原则)(Demeter Principle)
单一职责原则(Principle of single responsibility)
7,什么是单例模式
保证一个类只有一个实例,并且提供一个访问该全局访问点
8,单例模式优点和缺点
 优点:
 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
 提供了对唯一实例的受控访问。
 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
 允许可变数目的实例。
 避免对共享资源的多重占用。
 
 缺点:
 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
 单例类的职责过重,在一定程度上违背了“单一职责原则”。
 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
9,单例模式使用注意事项:
 使用时不能用反射模式创建单例,否则会实例化一个新的对象
 使用懒单例模式时注意线程安全问题
 饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)

多线程

1,多线程有什么用:
一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓"知其然知其所以然","会用"只是"知其然","为什么用"才是"知其所以然",只有达到"知其然知其所以然"的程度才可以说是把一个知识点运用自如。OK,下面说说我对这个问题的看法:
  
发挥多核CPU的优势
随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。
  
防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
  
便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
2,线程和进程有什么区别
根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位
在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行)
内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。
包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
3,创建线程的方式
比较常见的一个问题了,一般就是两种:
(1)继承Thread类
(2)实现Runnable接口
(3)实现Callable接口
至于哪个好,不用说肯定是后者好,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。
4,Runnable接口和Callable接口的区别
有点深的问题了,也看出一个Java程序员学习知识的广度。
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。
5,start()方法和run()方法的区别
只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。
6,volatile关键字的作用
一个非常重要的问题,是每个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的作用的前提是要理解Java内存模型,这里就不讲Java内存模型了,可以参见第31点,volatile关键字的作用主要有两个:
(1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据
(2)代码底层执行不像我们看到的高级语言----Java程序这么简单,它的执行是Java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。
7,什么是线程安全
又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。
这个问题有值得一提的地方,就是线程安全也是有几个级别的:
(1)不可变
像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
(2)绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet
(3)相对线程安全
相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。
(4)线程非安全
这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类
8,如何在两个线程之间共享数据
通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的
9,sleep方法和wait方法有什么区别
这个问题常问,sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器
10,ThreadLocal有什么用
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了
11,为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用
这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
12,wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别
wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
13,为什么要使用线程池
避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。 
14,synchronized和ReentrantLock的区别
synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
(1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
(2)ReentrantLock可以获取各种锁的信息
(3)ReentrantLock可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word,这点我不能确定。
15,ConcurrentHashMap的并发度是什么
ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?
16,Linux环境下如何查找哪个线程使用CPU最长
这是一个比较偏实践的问题,这种问题我觉得挺有意义的。可以这么做:
(1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过
(2)top -H -p pid,顺序不能改变
这样就可以打印出当前的项目,每条线程占用CPU时间的百分比。注意这里打出的是LWP,也就是操作系统原生线程的线程号,我笔记本山没有部署Linux环境下的Java工程,因此没有办法截图演示,网友朋友们如果公司是使用Linux环境部署项目的话,可以尝试一下。
使用"top -H -p pid"+"jps pid"可以很容易地找到某条占用CPU高的线程的线程堆栈,从而定位占用CPU高的原因,一般是因为不当的代码操作导致了死循环。
最后提一点,"top -H -p pid"打出来的LWP是十进制的,"jps pid"打出来的本地线程号是十六进制的,转换一下,就能定位到占用CPU高的线程的当前线程堆栈了。
17,怎么唤醒一个阻塞的线程
如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。   
18,什么是多线程的上下文切换
多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。
19,如果你提交任务时,线程池队列已满,这时会发生什么
这里区分一下:
如果使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务
如果使用的是有界队列比如ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy
20,Java中用到的线程调度算法是什么
抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。 
21,Thread.sleep(0)的作用是什么
由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。
22,什么是乐观锁和悲观锁
(1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
(2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
23,什么是CAS
CAS,全称为Compare and Swap,即比较-替换。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。    
24,单例模式的线程安全性
首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:
(1)饿汉式单例模式的写法:线程安全
(2)懒汉式单例模式的写法:非线程安全
(3)双检锁单例模式的写法:线程安全
25,线程类的构造方法、静态块是被哪个线程调用的
请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。
如果说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:
(1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的
(2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的
26,高并发、任务执行时间短的业务怎样使用线程池?
高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
并发不高、任务执行时间长的业务要区分开看:
 a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
 b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。
27,synchronized与Lock的区别
1)层次:前者是JVM实现,后者是JDK实现
2)功能:前者仅能实现互斥与重入,后者可以实现 可中断、可轮询、可定时、可公平、绑定多个条件、非块结构 synchronized在阻塞时不会响应中断,Lock会响应中断,并抛出InterruptedException异常。
3)异常:前者线程中抛出异常时JVM会自动释放锁,后者必须手工释放
4)性能:synchronized性能已经大幅优化,如果synchronized能够满足需求,则尽量使用synchronized

网络编程

1,java中IO流有哪些?
按数据流向:输入流和输出流
输入和输出都是从程序的角度来说的。
输入流:数据流向程序
输出流:数据从程序流出。

按处理单位:字节流和字符流
字节流:一次读入或读出是8位二进制
字符流:一次读入或读出是16位二进制
JDK 中后缀是 Stream 是字节流;后缀是 Reader,Writer 是字符流

按功能功能:节点流和处理流
节点流:直接与数据源相连,读入或写出

处理流:与节点流一块使用,在节点流的基础上,再套接一层

最根本的四大类:InputStream(字节输入流),OutputStream(字节输出流),Reader(字符输入流),Writer(字符输出流)
四大类的扩展,按处理单位区分
InputStream:FileInputStream、PipedInputStream、ByteArrayInputStream、BufferedInputstream、SequenceInputStream、DataInputStream、ObjectInputStream
OutputStream:FileOutputStream、PipedOutputStream、ByteArrayOutputStream、BufferedOutputStream、DataOutputStream、ObjectOutputStream、PrintStream
Reader:FileReader、PipedReader、CharArrayReader、BufferedReader、InputStreamReader
Writer:FileWriter、PipedWriter、CharArrayWriter、BufferedWriter、InputStreamWriter、PrintWriter

常用的流
对文件进行操作:FileInputStream(字节输入流)、FileOutputStream(字节输出流)、FileReader(字符输入流)、FileWriter(字符输出流)
对管道进行操作:PipedInputStream(字节输入流)、PipedOutStream(字节输出流)、PipedReader(字符输入流)、PipedWriter(字符输出流)
字节/字符数组:ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter
Buffered 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
字节转化成字符流:InputStreamReader、OutputStreamWriter
数据流:DataInputStream、DataOutputStream
打印流:PrintStream、PrintWriter
对象流:ObjectInputStream、ObjectOutputStream
序列化流:SequenceInputStream
2,tcp和udp的区别?
TCP/IP 协议是一个协议簇,包括很多协议。命名为 TCP/IP 协议的原因是 TCP 和 IP 这两个协议非常重要,应用很广。
TCP 和 UDP 都是 TCP/IP 协议簇里的一员。

TCP,Transmission Control Protocol 的缩写,即传输控制协议。
面向连接,即必须在双方建立可靠连接之后,才会收发数据
信息包头 20 个字节
建立可靠连接需要经过3次握手
断开连接需要经过4次挥手
需要维护连接状态
报文头里面的确认序号、累计确认及超时重传机制能保证不丢包、不重复、按序到达
拥有流量控制及拥塞控制的机制

UDP,User Data Protocol 的缩写,即用户数据报协议。
不建立可靠连接,无需维护连接状态
信息包头 8 个字节
接收端,UDP 把消息段放在队列中,应用程序从队列读消息
不受拥挤控制算法的调节
传送数据的速度受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制
面向数据报,不保证接收端一定能收到
3,tcp为什么要三次握手,两次不行吗?为什么?
两次握手只能保证单向连接是畅通的。
Step1       A -> B : 你好,B。
Step2       A <- B : 收到。你好,A。
这样的两次握手过程, A 向 B 打招呼得到了回应,即 A 向 B 发送数据,B 是可以收到的。
但是 B 向 A 打招呼,A 还没有回应,B 没有收到 A 的反馈,无法确保 A 可以收到 B 发送的数据。

只有经过第三次握手,才能确保双向都可以接收到对方的发送的 数据。
Step3       A -> B : 收到,B。
这样 B 才能确定 A 也可以收到 B 发送给 A 的数据
4,tcp粘包是怎么产生的?
1、什么是 tcp 粘包?
发送方发送的多个数据包,到接收方缓冲区首尾相连,粘成一包,被接收。

2、原因
发送端需要等缓冲区满才发送。如 TCP 协议默认使用 Nagle 算法可能会把多个数据包一次发送到接收方
接收方不及时接收缓冲区的包,造成多个包接收。如应用程读取缓存中的数据包的速度小于接收数据包的速度,缓存中的多个数据包会被应用程序当成一个包一次读取
 
3、处理方法
发送方使用 TCP_NODELAY 选项来关闭 Nagle 算法
数据包增加开始符和结束,应用程序读取、区分数据包
在数据包的头部定义整个数据包的长度,应用程序先读取数据包的长度,然后读取整个长度的包字节数据,保证读取的是单个包且完整
5,输入流和输出流的区别?
输入输出的方向是针对程序而言,向程序中读入数据,就是输入流;从程序中向外写出数据,就是输出流
从磁盘、网络、键盘读到内存,就是输入流,用 InputStream 或 Reader
写到磁盘、网络、屏幕,都是输出流,用 OuputStream 或 Writer
6,节点流和处理流区别
按流的处理位置分类
节点流:可以从某节点读数据或向某节点写数据的流。如 FileInputStream
处理流:对已存在的流的连接和封装,实现更为丰富的流数据处理,处理流的构造方法必需其他的流对象参数。如 BufferedReader 
7,字节流和字符流区别与适用场景
Java 中的字节流处理的最基本单位为 1 个字节,通常用来处理二进制数据。字节流类 InputStream 和 OutputStream 类均为抽象类,代表了基本的输入字节流和输出字节流。
Java 中的字符流处理的最基本的单元是 Unicode 代码单元(大小2字节),通常用来处理文本数据。

区别:
字节流操作的基本单元是字节;字符流操作的基本单元是字符
字节流默认不使用缓冲区;字符流使用缓冲区
字节流通常用于处理二进制数据,不支持直接读写字符;字符流通常用于处理文本数据
在读写文件需要对文本内容进行处理:按行处理、比较特定字符的时候一般会选择字符流;仅仅读写文件,不处理内容,一般选择字节流

特征:
以 stream 结尾都是字节流,reader 和 writer 结尾是字符流
InputStream 是所有字节输入流的父类,OutputStream 是所有字节输出流的父类
Reader 是字符输入流的父类,Writer 是字符输出流的父类

常见的字节流:
文件流:FileOutputStream 和 FileInputStream
缓冲流:BufferedOutputStream 和 BufferedInputStream
对象流:ObjectOutputStream 和 ObjectInputStream
 
常见的字符流:
字节转字符流:InputStreamReader 和 OutputStreamWriter
缓冲字符流:PrintWriter 和 BufferedReader
8,列举常用字节输入流和输出流
FileInputStream-FileOutputStream 文件数据读写
ObjectInputStream-ObjectOutputStream 对象数据读写
ByteArrayInputStream-ByteArrayOutputStream 内存字节数组读写
PipedInputStream-PipedOutputStream 管道输入输出
FilterInputStream-FilterOutputStream 过滤输入输出数据流
InputStreamReader-OutputStreamWriter 字节流转字符流
FileReader-FileWriter 文件字符输入输出流
BufferedReader-BufferedWriter 带缓冲的字符输入输出流  
9,缓冲流的优缺点
不带缓冲的流读取到一个字节或字符,就直接写出数据
带缓冲的流读取到一个字节或字符,先不输出,等达到了缓冲区的最大容量再一次性写出去
优点:减少了写出次数,提高了效率
缺点:接收端可能无法及时获取到数据
10,Java实现文件夹复制
package constxiong.interview;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;


/**
 * 复制文件夹
 * @author ConstXiong
 * @date 2019-11-13 13:38:19
 */
public class TestCopyDir {

    public static void main(String[] args) {
        String srcPath = "E:/a";
        String destPath = "E:/a_";
        copyDir(srcPath, destPath);
    }
    
    /**
     * 复制文件夹
     * @param srcFile
     * @param destFile
     */
    public static void copyDir(String srcDirPath, String destDirPath) {
        File srcDir = new File(srcDirPath);
        if (!srcDir.exists() || !srcDir.isDirectory()) {
            throw new IllegalArgumentException("参数错误");
        }
        File destDir = new File(destDirPath);
        if (!destDir.exists()) {
            destDir.mkdirs();
        }
        File[] files = srcDir.listFiles();
        for (File f : files) {
            if (f.isFile()) {
                copyFile(f, new File(destDirPath, f.getName()));
            } else if (f.isDirectory()) {
                copyDir(srcDirPath + File.separator + f.getName(),
                        destDirPath + File.separator + f.getName());
            }
        }
    }
    
    /**
     * 复制文件
     * @param srcFile
     * @param destFile
     */
    public static void copyFile(File srcFile, File destFile) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        byte[] b = new byte[1024];
        
        try {
            bis = new BufferedInputStream(new FileInputStream(srcFile));
            bos = new BufferedOutputStream(new FileOutputStream(destFile));
            int len;
            while ((len = bis.read(b)) > -1) {
                bos.write(b, 0, len);
            }
            bos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
11,Java中的Socket是什么?
Socket 也称作"套接字",用于描述 IP 地址和端口,是一个通信链的句柄,是应用层与传输层之间的桥梁
应用程序可以通过 Socket 向网络发出请求或应答网络请求
网络应用程序位于应用层,TCP 和 UDP 属于传输层协议,在应用层和传输层之间,使用 Socket 来进行连接
Socket 是传输层供给应用层的编程接口
Socket 编程可以开发客户端和服务器应用程序,可以在本地网络上进行通信,也可通过公网 Internet 在通信
12,基于TCP和UDP的Socket编程的主要步骤
JDK 在 java.net 包中为 TCP 和 UDP 两种通信协议提供了相应的 Socket 编程类
TCP 协议,服务端对应 ServerSocket,客户端对应 Socket
UDP 协议对应 DatagramSocket
基于 TCP 协议创建的套接字可以叫做流套接字,服务器端相当于一个监听器,用来监听端口,服务器与客服端之间的通讯都是输入输出流来实现的
基于 UDP 协议的套接字就是数据报套接字,客户端和服务端都要先构造好相应的数据包
 
基于 TCP 协议的 Socket 编程的主要步骤

服务端:
指定本地的端口创建 ServerSocket 实例, 用来监听指定端口的连接请求
通过 accept() 方法返回的 Socket 实例,建立了一个和客户端的新连接
通过 Sockect 实例获取 InputStream 和 OutputStream 读写数据
数据传输结束,调用 socket 实例的 close() 方法关闭连接

客户端:
指定的远程服务器 IP 地址和端口创建 Socket 实例
通过 Socket 实例获取 InputStream 和 OutputStream 来进行数据的读写
数据传输结束,调用 socket 实例的 close() 方法关闭连接

基于 UDP 协议的 Socket 编程的主要步骤
服务端:
指定本地端口创建 DatagramSocket 实例
通过字节数组,创建 DatagramPacket 实例,调用 DatagramSocket 实例的  receive() 方法,用 DatagramPacket 实例来接收数据
设置 DatagramPacket 实例返回的数据,调用 DatagramSocket 实例的 send() 方法来发送数据
数据传输完成,调用 DatagramSocket 实例的 close() 方法

客户端:
创建 DatagramSocket 实例
通过 IP 地址端口和数据创建 DatagramSocket 实例,调用 DatagramSocket 实例 send() 方法发送数据包
通过字节数组创建 DatagramSocket 实例,调用 DatagramSocket 实例 receive() 方法接受数据包
数据传输完成,调用 DatagramSocket 实例的 close() 方法
13,如何将字符串写入文件?
package constxiong.interview;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 测试写入字符串到文件
 * @author ConstXiong
 * @date 2019-11-08 12:05:49
 */
public class TestWriteStringToFile {

    public static void main(String[] args) {
        String cx = "ConstXiong";
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream("E:/a.txt");
            fos.write(cx.getBytes());//注意字符串编码
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

JVM

1,说一说JVM的内存区域
Java 虚拟机在执行 Java 程序的过程中会把他所管理的内存划分为若干个不同的数据区域:

程序计数器:可以看作是当前线程所执行的字节码文件(class)的行号指示器,它会记录执行痕迹,是每个线程私有的
方法区:主要存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据,该区域是被线程共享的,很少发生垃圾回收
栈:栈是运行时创建的,是线程私有的,生命周期与线程相同,存储声明的变量
本地方法栈:为 native 方法服务,native 方法是一种由非 java 语言实现的 java 方法,与 java 环境外交互,如可以用本地方法与操作系统交互
堆:堆是所有线程共享的一块内存,是在 java 虚拟机启动时创建的,几乎所有对象实例都在此创建,所以经常发生垃圾回收操作
JDK8 之前,Hotspot 中方法区的实现是永久代(Perm)
JDK8 开始使用元空间(Metaspace),以前永久代所有内容的字符串常量移至堆内存,其他内容移至元空间,元空间直接在本地内存分配
2,对象在哪块内存分配?
对象(数组可以理解为对象的一种)在堆内存分配
某些对象没有逃逸出方法,可能被优化为在栈上分配
3,JDK8为什么要使用元空间取代永久代?
永久代是 HotSpot VM 对方法区的实现,JDK 8 将其移除的部分原因如下:
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出
永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
将 HotSpot 与 JRockit 进行整合,JRockit 是没有永久代的
4,谈谈 JVM 中的常量池
JDK 1.8 开始
字符串常量池:存放在堆中,包括 String 对象执行 intern() 方法后存的地方、双引号直接引用的字符串
运行时常量池:存放在方法区,属于元空间,是类加载后的一些存储区域,大多数是类中 constant_pool 的内容
类文件常量池:constant_pool,JVM 定义的概念
5,介绍下Java中垃圾回收机制
什么样的对象会被当做垃圾回收?
当一个对象的地址没有变量去引用时,该对象就会成为垃圾对象,垃圾回收器在空闲的时候会对其进行内存清理回收
如何检验对象是否被回收?
可以重写 Object 类中的 finalize 方法,这个方法在垃圾收集器执行的时候,被收集器自动调用执行的
怎样通知垃圾收集器回收对象?
可以调用 System 类的静态方法 gc(),通知垃圾收集器去清理垃圾,但不能保证收集动作立即执行,具体的执行时间取决于垃圾收集的算法
6,Java中类加载过程是什么样的?
类加载的步骤为,加载 -> 验证 -> 准备 -> 解析 -> 初始化。
1、加载:
获取类的二进制字节流
将字节流代表的静态存储结构转化为方法区运行时数据结构
在堆中生成class字节码对象
2、验证:连接过程的第一步,确保 class 文件的字节流中的信息符合当前 JVM 的要求,不会危害 JVM 的安全
3、准备:为类的静态变量分配内存并将其初始化为默认值
4、解析:JVM 将常量池内符号引用替换成直接引用的过程
5、初始化:执行类构造器的初始化的过程
7,对象创建过程是什么样的?
对象在 JVM 中的创建过程如下:
JVM 会先去方法区找有没有所创建对象的类存在,有就可以创建对象了,没有则把该类加载到方法区
在创建类的对象时,首先会先去堆内存中分配空间
当空间分配完后,加载对象中所有的非静态成员变量到该空间下
所有的非静态成员变量加载完成之后,对所有的非静态成员进行默认初始化
所有的非静态成员默认初始化完成之后,调用相应的构造方法到栈中
在栈中执行构造函数时,先执行隐式,再执行构造方法中书写的代码
执行顺序:静态代码库,构造代码块,构造方法
当整个构造方法全部执行完,此对象创建完成,并把堆内存中分配的空间地址赋给对象名
8,JVM 如何确定垃圾对象?
JVM 采用的是可达性分析算法,通过 GC Roots 来判定对象是否存活,从 GC Roots 向下追溯、搜索,会产生 Reference Chain。当一个对象不能和任何一个 GC Root 产生关系时,就判定为垃圾。
软引用和弱引用,也会影响对象的回收。内存不足时会回收软引用对象;GC 时会回收弱引用对象。
9,被引用的对象就一定能存活吗?
不一定,看 Reference 类型,弱引用在 GC 时会被回收,软引用在内存不足的时候,即 OOM 前会被回收,但如果没有在 Reference Chain 中的对象就一定会被回收。
10,什么情况发生栈溢出?
-Xss可以设置线程栈的大小,当线程方法递归调用层次太深或者栈帧中的局部变量过多时,会出现栈溢出错误 java.lang.StackOverflowError
11,谈谈双亲委派模型
Parents Delegation Model,这里的 Parents 翻译成双亲有点不妥,类加载向上传递的过程中只有单亲;parents 更多的是多级向上的意思。
除了顶层的启动类加载器,其他的类加载器在加载之前,都会委派给它的父加载器进行加载,一层层向上传递,直到所有父类加载器都无法加载,自己才会加载该类。
双亲委派模型,更好地解决了各个类加载器协作时基础类的一致性问题,避免类的重复加载;防止核心API库被随意篡改。

JDK 9 之前
启动类加载器(Bootstrp ClassLoader),加载 /lib/rt.jar、-Xbootclasspath
扩展类加载器(Extension ClassLoader)sun.misc.Launcher$ExtClassLoader,加载 /lib/ext、java.ext.dirs
应用程序类加载器(Application ClassLoader,sun.misc.Launcher$AppClassLoader),加载 CLASSPTH、-classpath、-cp、Manifest
自定义类加载器
JDK 9 开始 Extension ClassLoader 被 Platform ClassLoader 取代,启动类加载器、平台类加载器、应用程序类加载器全都继承于 jdk.internal.loader.BuiltinClassLoader  
12,列举一些你知道的打破双亲委派机制的例子,为什么要打破?
JNDI 通过引入线程上下文类加载器,可以在 Thread.setContextClassLoader 方法设置,默认是应用程序类加载器,来加载 SPI 的代码。有了线程上下文类加载器,就可以完成父类加载器请求子类加载器完成类加载的行为。打破的原因,是为了 JNDI 服务的类加载器是启动器类加载,为了完成高级类加载器请求子类加载器(即上文中的线程上下文加载器)加载类。
Tomcat,应用的类加载器优先自行加载应用目录下的 class,并不是先委派给父加载器,加载不了才委派给父加载器。打破的目的是为了完成应用间的类隔离。
OSGi,实现模块化热部署,为每个模块都自定义了类加载器,需要更换模块时,模块与类加载器一起更换。其类加载的过程中,有平级的类加载器加载行为。打破的原因是为了实现模块热替换。
JDK 9,Extension ClassLoader 被 Platform ClassLoader 取代,当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责那个模块的加载器完成加载。打破的原因,是为了添加模块化的特性。   
13,如何找到死锁的线程?
死锁的线程可以使用 jstack 指令 dump 出 JVM 的线程信息。
jstack -l <pid> > threads.txt
有时候需要dump出现异常,可以加上 -F 指令,强制导出
jstack -F -l <pid> > threads.txt
如果存在死锁,一般在文件最后会提示找到 deadlock 的数量与线程信息
14,如何找到死锁的线程?
分为新生代和老年代,新生代默认占总空间的 1/3,老年代默认占 2/3。
新生代使用复制算法,有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1。
当新生代中的 Eden 区内存不足时,就会触发 Minor GC,过程如下:
在 Eden 区执行了第一次 GC 之后,存活的对象会被移动到其中一个 Survivor 分区;
Eden 区再次 GC,这时会采用复制算法,将 Eden 和 from 区一起清理,存活的对象会被复制到 to 区;
移动一次,对象年龄加 1,对象年龄大于一定阀值会直接移动到老年代
Survivor 区相同年龄所有对象大小的总和 > (Survivor 区内存大小 * 这个目标使用率)时,大于或等于该年龄的对象直接进入老年代。其中这个使用率通过 -XX:TargetSurvivorRatio 指定,默认为 50%
Survivor 区内存不足会发生担保分配
超过指定大小的对象可以直接进入老年代
Major GC,指的是老年代的垃圾清理,但并未找到明确说明何时在进行Major GC
FullGC,整个堆的垃圾收集,触发条件:
1.每次晋升到老年代的对象平均大小>老年代剩余空间
2.MinorGC后存活的对象超过了老年代剩余空间
3.元空间不足
4.System.gc() 可能会引起
5.CMS GC异常,promotion failed:MinorGC时,survivor空间放不下,对象只能放入老年代,而老年代也放不下造成;concurrent mode failure:GC时,同时有对象要放入老年代,而老年代空间不足造成
6.堆内存分配很大的对象
15,说说 JVM 如何执行 class 中的字节码
JVM 先加载包含字节码的 class 文件,存放在方法区,实际运行时,虚拟机会执行方法区内的代码。Java 虚拟机在内存中划分出栈和堆来存储运行时的数据。
运行过程中,每当调用进入 Java 方法,都会在 Java 方法栈中生成一个栈帧,用来支持虚拟机进行方法的调用与执行,包含了局部变量表、操作数栈、动态链接、方法返回地址等信息。
当退出当前执行的方法时,不管正常返回还是异常返回,Java 虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。
方法的调用,需要通过解析完成符号引用到直接引用;通过分派完成动态找到被调用的方法。
从硬件角度来看,Java 字节码无法直接执行。因此,Java 虚拟机需要将字节码翻译成机器码。翻译过程由两种形式:第一种是解释执行,即将遇到的字节一边码翻译成机器码一边执行;第二种是即时编译(Just-In-Time compilation,JIT),即将一个方法中包含的所有字节码编译成机器码后再执行。在 HotSpot 里两者都有,解释执行在启动时节约编译时间执行速度较快;随着时间的推移,编译器逐渐会返回作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率。
16,生产环境 CPU 占用过高,你如何解决?
top + H 指令找出占用 CPU 最高的进程的 pid
top -H -p
在该进程中找到,哪些线程占用的 CPU 最高的线程,记录下 tid
jstack -l > threads.txt,导出进程的线程栈信息到文本,导出出现异常的话,加上 -F 参数
将 tid 转换为十六进制,在 threads.txt 中搜索,查到对应的线程代码执行栈,在代码中查找占 CPU 比较高的原因。其中 tid 转十六进制,可以借助 Linux 的 printf "%x" tid 指令
我用上述方法查到过,jvm 多条线程疯狂 full gc 导致的CPU 100% 的问题和 JDK1.6 HashMap 并发 put 导致线程 CPU 100% 的问题
17,生产环境服务器变慢,如何诊断处理?
使用 top 指令,服务器中 CPU 和 内存的使用情况,-H 可以按 CPU 使用率降序,-M 内存使用率降序。排除其他进程占用过高的硬件资源,对 Java 服务造成影响。
如果发现 CPU 使用过高,可以使用 top 指令查出 JVM 中占用 CPU 过高的线程,通过 jstack 找到对应的线程代码调用,排查出问题代码。
如果发现内存使用率比较高,可以 dump 出 JVM 堆内存,然后借助 MAT 进行分析,查出大对象或者占用最多的对象来自哪里,为什么会长时间占用这么多;如果 dump 出的堆内存文件正常,此时可以考虑堆外内存被大量使用导致出现问题,需要借助操作系统指令 pmap 查出进程的内存分配情况、gdb dump 出具体内存信息、perf 查看本地函数调用等。
如果 CPU 和 内存使用率都很正常,那就需要进一步开启 GC 日志,分析用户线程暂停的时间、各部分内存区域 GC 次数和时间等指标,可以借助 jstat 或可视化工具 GCeasy 等,如果问题出在 GC 上面的话,考虑是否是内存不够、根据垃圾对象的特点进行参数调优、使用更适合的垃圾收集器;分析 jstack 出来的各个线程状态。如果问题实在比较隐蔽,考虑是否可以开启 jmx,使用 visualmv 等可视化工具远程监控与分析。    
18,invokedynamic 指令是干什么的?
Java 7 开始,新引入的字节码指令,可以实现一些动态类型语言的功能。Java 8 的 Lambda 表达式就是通过 invokedynamic 指令实现,使用方法句柄实现。   

Spring

1,Spring是什么?
Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。主要包括以下七个模块:
Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);
Spring Core:核心类库,所有功能都依赖于该类库,提供IOC和DI服务;
Spring AOP:AOP服务;
Spring Web:提供了基本的面向Web的综合特性,提供对常见框架如Struts2的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也能在这些框架的前后插入拦截器;
Spring MVC:提供面向Web应用的Model-View-Controller,即MVC实现。
Spring DAO:对JDBC的抽象封装,简化了数据访问异常的处理,并能统一管理JDBC事务;
Spring ORM:对现有的ORM框架的支持;
2,Spring有哪些优点?
(1)spring属于低侵入式设计,代码的污染极低;
(2)spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性;
(3)Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
(4)spring对于主流的应用框架提供了集成支持。
3,Spring的IOC理解?
(1)什么是IOC:
     IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。
     最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。而 IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转。
(2)什么是DI:
     IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性
(3)IoC的原理:
     Spring 的 IoC 的实现原理就是工厂模式加反射机制,而在 Spring 容器中,Bean 对象如何注册到 IoC 容器,以及Bean对象的加载、实例化、初始化
4,Spring的AOP理解?
 OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。
 AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
 AOP实现的关键在于 代理模式,AOP代理主要分为静态代理和动态代理。静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
 (1)AspectJ是静态代理,也称为编译时增强,AOP框架会在编译阶段生成AOP代理类,并将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
 (2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
 Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
  ① JDK动态代理只提供接口的代理,不支持类的代理,要求被代理类实现接口。JDK动态代理的核心是InvocationHandler接口和Proxy类,在获取代理对象时,使用Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;
  InvocationHandler 的 invoke(Object  proxy,Method  method,Object[] args):proxy是最终生成的代理对象;  method 是被代理目标实例的某个具体方法;  args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
  ② 如果被代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
 (3)静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
 IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。
5,Spring AOP里面的几个名词的概念
(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。 
(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。
在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。
(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。
切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。
(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。
(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。
(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。
(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
6,Spring AOP里面的几个名词的概念
(1)初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中:
    ① 实例化BeanFactory【DefaultListableBeanFactory】工厂,用于生成Bean对象
    ② 实例化BeanDefinitionReader注解配置读取器,用于对特定注解(如@Service、@Repository)的类进行读取转化成  BeanDefinition 对象,(BeanDefinition 是 Spring 中极其重要的一个概念,它存储了 bean 对象的所有特征信息,如是否单例,是否懒加载,factoryBeanName 等)
    ③ 实例化ClassPathBeanDefinitionScanner路径扫描器,用于对指定的包目录进行扫描查找 bean 对象
(2)将配置类的BeanDefinition注册到容器中:
(3)调用refresh()方法刷新容器:
    ① prepareRefresh()刷新前的预处理:
    ② obtainFreshBeanFactory():获取在容器初始化时创建的BeanFactory:
    ③ prepareBeanFactory(beanFactory):BeanFactory的预处理工作,向容器中添加一些组件:
    ④ postProcessBeanFactory(beanFactory):子类重写该方法,可以实现在BeanFactory创建并预处理完成以后做进一步的设置
    ⑤ invokeBeanFactoryPostProcessors(beanFactory):在BeanFactory标准初始化之后执行BeanFactoryPostProcessor的方法,即BeanFactory的后置处理器:
    ⑥ registerBeanPostProcessors(beanFactory):向容器中注册Bean的后置处理器BeanPostProcessor,它的主要作用是干预Spring初始化bean的流程,从而完成代理、自动注入、循环依赖等功能
    ⑦ initMessageSource():初始化MessageSource组件,主要用于做国际化功能,消息绑定与消息解析:
    ⑧ initApplicationEventMulticaster():初始化事件派发器,在注册监听器时会用到:
    ⑨ onRefresh():留给子容器、子类重写这个方法,在容器刷新的时候可以自定义逻辑
    ⑩ registerListeners():注册监听器:将容器中所有的ApplicationListener注册到事件派发器中,并派发之前步骤产生的事件:
    ⑪ finishBeanFactoryInitialization(beanFactory):初始化所有剩下的单实例bean,核心方法是preInstantiateSingletons(),会调用getBean()方法创建对象;
    ⑫ finishRefresh():发布BeanFactory容器刷新完成事件:
7,BeanFactory和ApplicationContext有什么区别?
 BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。
(1)BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理。ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:
继承MessageSource,因此支持国际化。
资源文件访问,如URL和文件(ResourceLoader)。
载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
提供在监听器中注册bean的事件。
(2)①BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。
    ②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 
    ③ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢。
(3)BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。
(4)BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。
8,Spring Bean的生命周期?
简单来说,Spring Bean的生命周期只有四个阶段:实例化 Instantiation --> 属性赋值 Populate  --> 初始化 Initialization  --> 销毁 Destruction
 (1)实例化Bean:
对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。
对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。
(2)设置对象属性(依赖注入):实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入。
(3)处理Aware接口:Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:
    ①如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字;
    ②如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例。
    ②如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身。
    ③如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)方法,传入Spring上下文;
(4)BeanPostProcessor前置处理:如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法。
(5)InitializingBean:如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。
(6)init-method:如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。
(7)BeanPostProcessor后置处理:如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;
以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了。
(8)DisposableBean:当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法;
(9)destroy-method:最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。
9,Spring中bean的作用域
singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。
prototype:为每一个bean请求创建一个实例。
request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。
global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。
10,Spring基于xml注入bean的几种方式
set()方法注入;
构造器注入:①通过index设置参数的位置;②通过type设置参数类型;
静态工厂注入;
实例工厂;
11,Spring如何解决循环依赖问题:
循环依赖问题在Spring中主要有三种情况:
(1)通过构造方法进行依赖注入时产生的循环依赖问题。
(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:
第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储。
12,Spring事务的实现方式和实现原理:
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过 redo log 和 undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。
(1)Spring事务的种类:
spring支持编程式事务管理和声明式事务管理两种方式:
    ①编程式事务管理使用TransactionTemplate。
    ②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
(2)spring的事务传播机制:
spring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。
    ① PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。
    ② PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。
    ③ PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘
    ④ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    ⑤ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。
    ⑥ PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。
    ⑦ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
(3)Spring中的隔离级别:
    ① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。
    ② ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。
    ③ ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。
    ④ ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。
    ⑤ ISOLATION_SERIALIZABLE:所有事务逐个依次执行。
13,Spring事务的实现方式和实现原理:
spring使用的设计模式
工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
单例模式:Bean默认为单例模式
策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate
适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring MVC中也是用到了适配器模式适配Controller
观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。
桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
13,Spring常用的注解有哪些?
一.用于创建对象的注解
作用:和在xml配置文件中编写一个<bean>标签实现的功能一样。
1.@Component : 用于把当前类对象存入Spring容器中。
属性:value --- 用于指定bean的id。如果不写该属性,id的默认值是当前类名,且首字母改为小写。
2.@Controller : 一般用在表现层。
3.@Service : 一般用在业务层。
4.@Repository : 一般用在持久层(dto层)。
备注:其中2,3,4注解的作用和属性与@Component 一模一样,他们三个是Spring框架为我们提供的三层架构使用的注解,使我们对三层对象更加清晰。

二.用于注入数据的注解
作用:和在xml配置文件中的
<bean>标签中写一个<property>标签的功能一样。
1.@Autowired : 自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。可以作用在变量或者方法上。
2.@Qualifier : 在按照类型注入的基础之上再按照名称注入,它在给类成员注入时要和@Autowired配合使用,但是在给方法参数注入是可以单独使用。
3.@Resource : 直接按照bean的id注入,可以独立使用。
属性:name --- 用于指定bean的id。
备注:以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml来实现。
4.@Value : 用于注入基本类型和String类型的数据。
属性:value --- 用于指定数据的值,它可以使用Spring中的SpEL(也就是Spring中的el表达式)。SpEL的写法:${表达式}

三.用于改变作用范围的注解
作用:和在xml配置文件中的<bean>标签中使用scope属性实现的功能一样。
1.@Scope : 用于指定bean的作用范围。
属性:value --- 指定范围的取值。常用取值:singleton(单例)和prototype(多例)。

四.和生命周期相关的注解
作用:和在xml配置文件中的<bean>标签中使用init-method和destory-method属性实现的功能一样。
1.@PreDestory : 用于指定销毁方法。
2.@PostConstruct : 用于指定初始化方法。

五.Spring新注解
1.@Configuration : 用于指定当前类是一个配置类。
2.@ComponentScan : 用于通过注解指定Spring在创建容器时要扫描的包。
3.@Bean : 用于把当前方法的返回值作为bean对象存入Spring的IOC容器中。
属性:name --- 用于指定bean的id。当不写时,默认值为当前方法的名称。
4.@Import : 用于导入其他的配置类。
属性:value --- 用于指定其他配置类的字节码。当我们使用@Import注解时,有@Import注解的类就是父配置类。
5.@PropertySource : 用于指定properties文件的位置。
属性:value --- 指定文件的名称和路径。关键字classpath表示类路径下。     
14,spring DAO 有什么用?
Spring DAO(数据访问对象) 使得 JDBC,Hibernate 或 JDO 这样的数据访问技术更容易以一种统一的方式工作。这使得用户容易在持久性技术之间切换。它还允许您在编写代码时,无需考虑捕获每种技术不同的异常。 
15,Spring框架的事务管理有哪些优点?
为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
为编程式事务管理提供了一套简单的API而不是一些复杂的事务API
支持声明式事务管
和Spring各种数据访问抽象层很好得集成。
16,JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例, 生成目标类的代理对象。
如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。
InvocationHandler 的 invoke(Object proxy,Method method,Object[] args):proxy是最终生成的代理实例; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。
17,将一个类声明为Spring的 bean 的注解有哪些?
我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:
@Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component 注解标注。 8 @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
@Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
@Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。   
18,AOP 有哪些实现方式?
实现 AOP 的技术,主要分为两大类:
静态代理 - 指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;
编译时编织(特殊编译器实现)
类加载时编织(特殊的类加载器实现)。
动态代理 - 在运行时在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
JDK 动态代理:通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口 。JDK 动态代理的核心是 InvocationHandler 接口和 Proxy 类 。
CGLIB动态代理: 如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类 。CGLIB ( Code Generation Library ),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意, CGLIB 是通过继承的方式做的动态代理,因此如果某个类被标记为 final ,那么它是无法使用 CGLIB 做动态代理的。
19,Spring AOP and AspectJ AOP 有什么区别?
Spring AOP 基于动态代理方式实现;AspectJ 基于静态代理方式实现。 Spring AOP 仅支持方法级别的 PointCut;提供了完全的 AOP 支持,它还支持属性级别的 PointCut。
20,Spring中出现同名bean怎么办?
同一个配置文件内同名的Bean,以最上面定义的为准
不同配置文件中存在同名Bean,后解析的配置文件会覆盖先解析的配置文件
同文件中ComponentScan和@Bean出现同名Bean。同文件下@Bean的会生效,@ComponentScan扫描进来不会生效。通过@ComponentScan扫描进来的优先级是最低的,原因就是它扫描进来的Bean定义是最先被注册的~
21,SpringMVC 工作原理了解吗?
流程说明:
客户端(浏览器)发送请求,直接请求到 DispatcherServlet。
DispatcherServlet 根据请求信息调用 HandlerMapping,解析请求对应的 Handler。
解析到对应的 Handler(也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
HandlerAdapter 会根据 Handler来调用真正的处理器开处理请求,并处理相应的业务逻辑。
处理器处理完业务后,会返回一个 ModelAndView 对象,Model 是返回的数据对象,View 是个逻辑上的 View。
ViewResolver 会根据逻辑 View 查找实际的 View。
DispaterServlet 把返回的 Model 传给 View(视图渲染)。
把 View 返回给请求者(浏览器)   
22,spring容器的bean什么时候被实例化?
1,如果你使用BeanFactory作为Spring Bean的工厂类,则所有的bean都是在第一次使用该Bean的时候实例化
2,如果你使用ApplicationContext作为Spring Bean的工厂类,则又分为以下几种情况:
如果bean的scope是singleton的,并且lazy-init为false(默认是false,所以可以不用设置),则 ApplicationContext启动的时候就实例化该Bean,并且将实例化的Bean放在一个map结构的缓存中,下次再使 用该 Bean的时候,直接从这个缓存中取
如果bean的scope是singleton的,并且lazy-init为true,则该Bean的实例化是在第一次使用该Bean的时候进行实例化。
如果bean的scope是prototype的,则该Bean的实例化是在第一次使用该Bean的时候进行实例化
23,构造方法注入和设值注入有什么区别?
在设值注入方法支持大部分的依赖注入,如果我们仅需要注入int、 string和long型的变量,我们不要用设值的方法注入。对于基本类型,如果 我们没有注入的话,可以为基本类型设置默认值。在构造方法注入不支持 大部分的依赖注入,因为在调用构造方法中必须传入正确的构造参数,否则的话为报错。
设值注入不会重写构造方法的值。如果我们对同一个变量同时使用了构 造方法注入又使用了设置方法注入的话,那么构造方法将不能覆盖由设值 方法注入的值。很明显,因为构造方法尽在对象被创建时调用。
在使用设值注入时有可能还不能保证某种依赖是否已经被注入,也就是 说这时对象的依赖关系有可能是不完整的。而在另一种情况下,构造器注 入则不允许生成依赖关系不完整的对象。
在设值注入时如果对象A和对象B互相依赖,在创建对象A时Spring会拋 出ObjectCurrentlyInCreationException异常,因为在B对象被创建之前 A对象是不能被创建的,反之亦然。所以Spring用设值注入的方法解决 了循环依赖的问题,因对象的设值方法是在对象被创建之前被调用的。   

SpringBoot

1,什么是SpringBoot?
用来简化Spring应用的初始搭建以及开发过程,使用特定的方式来进行配置
创建独立的Spring引用程序main方法运行
嵌入的tomcat无需部署war文件
简化maven配置
自动配置Spring添加对应的功能starter自动化配置
SpringBoot来简化Spring应用开发,约定大于配置,去繁化简
2,Spring Boot 有哪些优点?
容易上手,提升开发效率,为 Spring 开发提供一个更快、更广泛的入门体验。
开箱即用,远离繁琐的配置。
提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。
没有代码生成,也不需要XML配置。
避免大量的 Maven 导入和各种版本冲突。
3,Spring Boot 的核心注解是哪个?它主要由哪几个注解组成的?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
@ComponentScan:Spring组件扫描。   
4,Spring Boot 自动配置原理是什么?
注解 @EnableAutoConfiguration, @Configuration, @ConditionalOnClass 就是自动配置的核心,
@EnableAutoConfiguration 给容器导入META-INF/spring.factories 里定义的自动配置类。
筛选有效的自动配置类。
每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能   
5,Spring Boot 中的 starter 到底是什么 ?
首先,这个 Starter 并非什么新的技术点,基本上还是基于 Spring 已有功能来实现的。首先它提供了一个自动化配置类,一般命名为 XXXAutoConfiguration ,在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是 Spring 中原本就有的),然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置,然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了。当然,开发者也可以自定义 Starter
6,Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?
Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar 命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。
Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes 目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。
7,运行 Spring Boot 有哪几种方式?
1)打包用命令或者放到容器中运行
2)用 Maven/ Gradle 插件运行
3)直接执行 main 方法运行
8,Spring Boot 需要独立的容器运行吗?
可以不需要,内置了 Tomcat/ Jetty 等容器。   
9,如何使用 Spring Boot 实现异常处理?
Spring 提供了一种使用 ControllerAdvice 处理异常的非常有用的方法。 我们通过实现一个 ControlerAdvice 类,来处理控制器类抛出的所有异常。
10,Spring 和 SpringBoot 有什么不同?
Spring 框架提供多种特性使得 web 应用开发变得更简便,包括依赖注入、数据绑定、切面编程、数据存取等等。
随着时间推移,Spring 生态变得越来越复杂了,并且应用程序所必须的配置文件也令人觉得可怕。这就是 Spirng Boot 派上用场的地方了 – 它使得 Spring 的配置变得更轻而易举。
实际上,Spring 是 unopinionated(予以配置项多,倾向性弱) 的,Spring Boot 在平台和库的做法中更 opinionated ,使得我们更容易上手。
这里有两条 SpringBoot 带来的好处:
根据 classpath 中的 artifacts 的自动化配置应用程序
提供非功能性特性例如安全和健康检查给到生产环境中的应用程序
11,Spring 和 SpringBoot 有什么不同?
Spring boot actuator 是 spring 启动框架中的重要功能之一。Spring boot 监视器可帮助您访问生产环境中正在运行的应用程序的当前状态。有几个指标必须在生产环境中进行检查和监控。即使一些外部应用程序可能正在使用这些服务来向相关人员触发警报消息。监视器模块公开了一组可直接作为 HTTP URL 访问的REST 端点来检查状态。
12,如何在自定义端口上运行 Spring Boot 应用程序?
为了在自定义端口上运行 Spring Boot 应用程序,您可以在application.properties 中指定端口。server.port = 8090
13,如何实现 Spring Boot 应用程序的安全性?
为了实现 Spring Boot 的安全性,我们使用 spring-boot-starter-security 依赖项,并且必须添加安全配置。它只需要很少的代码。配置类将必须扩展WebSecurityConfigurerAdapter 并覆盖其方法。
14,SpringBoot starter 作用在什么地方?
依赖管理是所有项目中至关重要的一部分。当一个项目变得相当复杂,管理依赖会成为一个噩梦,因为当中涉及太多 artifacts 了。
这时候 SpringBoot starter 就派上用处了。每一个 stater 都在扮演着提供我们所需的 Spring 特性的一站式商店角色。其他所需的依赖以一致的方式注入并且被管理。
所有的 starter 都归于 org.springframework.boot 组中,并且它们都以由 spring-boot-starter- 开头取名。这种命名方式使得我们更容易找到 starter 依赖,特别是当我们使用那些支持通过名字查找依赖的 IDE 当中。
在写这篇文章的时候,已经有超过50个 starter了,其中最常用的是:
spring-boot-starter:核心 starter,包括自动化配置支持,日志以及 YAML
spring-boot-starter-aop:Spring AOP 和 AspectJ 相关的切面编程 starter
spring-boot-starter-data-jpa:使用 Hibernate Spring Data JPA 的 starter
spring-boot-starter-jdbc:使用 HikariCP 连接池 JDBC 的 starter
spring-boot-starter-security:使用 Spring Security 的 starter
spring-boot-starter-test:SpringBoot 测试相关的 starter
spring-boot-starter-web:构建 restful、springMVC 的 web应用程序的 starter    
15,怎么使用 SpringBoot 去执行命令行程序?
像其他 Java 程序一样,一个 SpringBoot 命令行程序必须要有一个 main 方法。这个方法作为一个入口点,通过调用 SpringApplication#run 方法来驱动程序执行:
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class);
        // other statements
    }
}    
SpringApplication 类会启动一个 Spirng 容器以及自动化配置 beans。
要注意的是我们必须把一个配置类传递到 run 方法中作为首要配置资源。按照惯例,这个参数一般是入口类本身。
在调用 run 方法之后,我们可以像平常的程序一样执行其他语句。     
16,什么是 JavaConfig?
Spring JavaConfig 是 Spring 社区的产品,它提供了配置 Spring IoC 容器的纯Java 方法。因此它有助于避免使用 XML 配置。使用 JavaConfig 的优点在于:
(1)面向对象的配置。由于配置被定义为 JavaConfig 中的类,因此用户可以充分利用 Java 中的面向对象功能。一个配置类可以继承另一个,重写它的@Bean 方法等。
(2)减少或消除 XML 配置。基于依赖注入原则的外化配置的好处已被证明。但是,许多开发人员不希望在 XML 和 Java 之间来回切换。JavaConfig 为开发人员提供了一种纯 Java 方法来配置与 XML 配置概念相似的 Spring 容器。从技术角度来讲,只使用 JavaConfig 配置类来配置容器是可行的,但实际上很多人认为将JavaConfig 与 XML 混合匹配是理想的。
(3)类型安全和重构友好。JavaConfig 提供了一种类型安全的方法来配置 Spring容器。由于 Java 5.0 对泛型的支持,现在可以按类型而不是按名称检索 bean,不需要任何强制转换或基于字符串的查找。      
17,什么是 JavaConfig?
YAML 现在可以算是非常流行的一种配置文件格式了,无论是前端还是后端,都可以见到 YAML 配置。那么 YAML 配置和传统的 properties 配置相比到底有哪些优势呢?
配置有序,在一些特殊的场景下,配置有序很关键
支持数组,数组中的元素可以是基本数据类型也可以是对象
简洁
相比 properties 配置文件,YAML 还有一个缺点,就是不支持 @PropertySource 注解导入自定义的 YAML 配置。
18,为什么我们需要 spring-boot-maven-plugin?
spring-boot-maven-plugin 提供了一些像 jar 一样打包或者运行应用程序的命令。
spring-boot:run 运行你的 SpringBooty 应用程序。
spring-boot:repackage 重新打包你的 jar 包或者是 war 包使其可执行
spring-boot:start 和 spring-boot:stop 管理 Spring Boot 应用程序的生命周期(也可以说是为了集成测试)。
spring-boot:build-info 生成执行器可以使用的构造信息 
19,Spring Boot 有哪几种读取配置的方式?
@PropertySource
@Value
@Environment
@ConfigurationPropertie
20,Spring Boot扫描流程?
1、调用run方法中的refreshContext方法
2、用AbstractApplicationContext中的refresh方法
3、委托给invokeBeanFactoryPostProcessors去处理调用链
4、其中一个方法postProcessBeanDefinitionRegistry会去调用processConfigBeanDefinitions解析beandefinitions
5、在processConfigBeanDefinitions中有一个parse方法,其中有componentScanParser.parse的方法,这个方法会扫描当前路径下所有Component组件
20,Spring Boot初始化环境变量流程?
1、调用prepareEnvironment方法去设置环境变量
2、接下来有三个方法getOrCreateEnvironment,configureEnvironment,environmentPrepared
3、getOrCreateEnvironment去初始化系统环境变量
4、configureEnvironment去初始化命令行参数
5、environmentPrepared当广播到来的时候调用onApplicationEnvironmentPreparedEvent方法去使用postProcessEnvironment方法load yml和properties变量
21,Spring Boot监听器流程?
1、通过app.addListeners注册进入
2、初始化一个SpringApplicationRunListeners进行处理
3、从spring.factories中读取监听器处理类EventPublishingRunListener
4、通过createSpringFactoriesInstances创建监听器处理类实例
5、调用监听器listeners.starting()的方法来启动。
6、底层把事件处理交给线程池去处理

SpringCloud

1, Spring Cloud 是什么
 Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、智能路由、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
 Spring Cloud并没有重复制造轮子,它只是将各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
2, Spring Boot 中如何实现定时任务 ?
 定时任务也是一个常见的需求,Spring Boot 中对于定时任务的支持主要还是来自 Spring 框架。
 在 Spring Boot 中使用定时任务主要有两种不同的方式,一个就是使用 Spring 中的 @Scheduled 注解,另一个则是使用第三方框架 Quartz。
 使用 Spring 中的 @Scheduled 的方式主要通过 @Scheduled 注解来实现。
 使用 Quartz ,则按照 Quartz 的方式,定义 Job 和 Trigger 即可。
3, 为什么需要学习Spring Cloud
首先springcloud基于spingboot的优雅简洁,可还记得我们被无数xml支配的恐惧?可还记得 springmvc,mybatis错综复杂的配置,有了spingboot,这些东西都不需要了,spingboot好处不 再赘诉,springcloud就基于SpringBoot把市场上优秀的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理
什么叫做开箱即用?即使是当年的黄金搭档dubbo+zookeeper下载配置起来也是颇费心神的!而springcloud完成这些只需要一个jar的依赖就可以了!
springcloud大多数子模块都是直击痛点,像zuul解决的跨域,fegin解决的负载均衡,hystrix的熔断机制等等等等
4,SpringCloud由什么组成
这就有很多了,我讲几个开发中最重要的
Spring Cloud Eureka:服务注册与发现
Spring Cloud Zuul:服务网关
Spring Cloud Ribbon:客户端负载均衡
Spring Cloud Feign:声明性的Web服务客户端
Spring Cloud Hystrix:断路器
Spring Cloud Confifig:分布式统一配置管理
4,Spring Cloud 和dubbo区别?
服务调用方式:dubbo是RPC springcloud Rest Api
注册中心:dubbo 是zookeeper springcloud是eureka,也可以是zookeeper
服务网关,dubbo本身没有实现,只能通过其他第三方技术整合,springcloud有Zuul路由网关,作为路由服务器,进行消费者的请求分发,springcloud支持断路器,与git完美集成配置文件支持版本控制,事物总线实现配置文件的更新与服务自动装配等等一系列的微服务架构要素。

Mybatis

Dubbo

Nginx

Docker

ElasticSearch

Zookeeper

Nacos

Eureka

MQ

1,MQ 是什么?为什么使用?
MQ(Message Queue)消息队列,是 "先进先出" 的一种数据结构。
MQ 的作用:一般用来解决应用解耦,异步处理,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构。

应用解耦:
当 A 系统生产关键数据,发送数据给多个其他系统消费,此时 A 系统和其他系统产生了严重的耦合,如果将 A 系统产生的数据放到 MQ 当中,其他系统去 MQ 获取消费数据,此时各系统独立运行只与 MQ 交互,添加新系统消费 A 系统的数据也不需要去修改 A 系统的代码,达到了解耦的效果。

异步处理:
互联网类企业对用户的直接操作,一般要求每个请求在 200ms 以内完成。对于一个系统调用多个系统,不使用 MQ 的情况下,它执行完返回的耗时是调用完所有系统所需时间的总和;使用 MQ 进行优化后,执行的耗时则是执行主系统的耗时加上发送数据到消息队列的耗时,大幅度提升系统性能和用户体验。
 
流量削峰:
MySQL 每秒最高并发请求在 2000 左右,用户访问量高峰期的时候涌入的大量请求,会将 MySQL 打死,然后系统就挂掉,但过了高峰期,请求量可能远低于 2000,这种情况去增加服务器就不值得,如果使用 MQ 的情况,将用户的请求全部放到 MQ 中,让系统去消费用户的请求,不要超过系统所能承受的最大请求数量,保证系统不会再高峰期挂掉,高峰期过后系统还是按照最大请求数量处理完请求。
2,使用 MQ 的缺陷有哪些?
系统可用性降低:以前只要担心系统的问题,现在还要考虑 MQ 挂掉的问题,MQ 挂掉,所关联的系统都会无法提供服务。
系统复杂性变高:要考虑消息丢失、消息重复消费等问题。
一致性问题:多个 MQ 消费系统,部分成功,部分失败,要考虑事务问题。
3,MQ 有哪些使用场景?
异步处理:用户注册后,发送注册邮件和注册短信。用户注册完成后,提交任务到 MQ,发送模块并行获取 MQ 中的任务。
系统解耦:比如用注册完成,再加一个发送微信通知。只需要新增发送微信消息模块,从 MQ 中读取任务,发送消息即可。无需改动注册模块的代码,这样注册模块与发送模块通过 MQ 解耦。
流量削峰:秒杀和抢购等场景经常使用 MQ 进行流量削峰。活动开始时流量暴增,用户的请求写入 MQ,超过 MQ 最大长度丢弃请求,业务系统接收 MQ 中的消息进行处理,达到流量削峰、保证系统可用性的目的。
日志处理:日志采集方收集日志写入 kafka 的消息队列中,处理方订阅并消费 kafka 队列中的日志数据。
消息通讯:点对点或者订阅发布模式,通过消息进行通讯。如微信的消息发送与接收、聊天室等。    
4,如何保证MQ的高可用?
ActiveMQ:
Master-Slave 部署方式主从热备,方式包括通过共享存储目录来实现(shared filesystem Master-Slave)、通过共享数据库来实现(shared database Master-Slave)、5.9版本后新特性使用 ZooKeeper 协调选择 master(Replicated LevelDB Store)。
Broker-Cluster 部署方式进行负载均衡。

RabbitMQ:
单机模式与普通集群模式无法满足高可用,镜像集群模式指定多个节点复制 queue 中的消息做到高可用,但消息之间的同步网络性能开销较大。

RocketMQ:
有多 master 多 slave 异步复制模式和多 master 多 slave 同步双写模式支持集群部署模式。
Producer 随机选择 NameServer 集群中的其中一个节点建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Master 发送心跳,只能将消息发送到 Broker master。
Consumer 同时与提供 Topic 服务的 Master、Slave 建立长连接,从 Master、Slave 订阅消息都可以,订阅规则由 Broker 配置决定。

Kafka:
由多个 broker 组成,每个 broker 是一个节点;topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 存放一部分数据,这样每个 topic 的数据就分散存放在多个机器上的。
replica 副本机制保证每个 partition 的数据同步到其他节点,形成多 replica 副本;所有 replica 副本会选举一个 leader 与 Producer、Consumer 交互,其他 replica 就是 follower;写入消息 leader 会把数据同步到所有 follower,从 leader 读取消息。
每个 partition 的所有 replica 分布在不同的机器上。某个 broker 宕机,它上面的 partition 在其他节点有副本,如果有 partition 的 leader,会进行重新选举 leader。
5,如何保证消息不被重复消费?
消息被重复消费,就是消费方多次接受到了同一条消息。根本原因就是,第一次消费完之后,消费方给 MQ 确认已消费的反馈,MQ 没有成功接受。比如网络原因、MQ 重启等。
所以 MQ 是无法保证消息不被重复消费的,只能业务系统层面考虑。
不被重复消费的问题,就被转化为消息消费的幂等性的问题。幂等性就是指一次和多次请求的结果一致,多次请求不会产生副作用。
保证消息消费的幂等性可以考虑下面的方式:
给消息生成全局 id,消费成功过的消息可以直接丢弃
消息中保存业务数据的主键字段,结合业务系统需求场景进行处理,避免多次插入、是否可以根据主键多次更新而并不影响结果等
6,你了解哪些常用的 MQ?
ActiveMQ:支持万级的吞吐量,较成熟完善;官方更新迭代较少,社区的活跃度不是很高,有消息丢失的情况。
RabbitMQ:延时低,微秒级延时,社区活跃度高,bug 修复及时,而且提供了很友善的后台界面;用 Erlang 语言开发,只熟悉 Java 的无法阅读源码和自行修复 bug。
RocketMQ:阿里维护的消息中间件,可以达到十万级的吞吐量,支持分布式事务。
Kafka:分布式的中间件,最大优点是其吞吐量高,一般运用于大数据系统的实时运算和日志采集的场景,功能简单,可靠性高,扩展性高;缺点是可能导致重复消费。
7,消息大量积压怎么解决?
消息的积压来自于两方面:要么发送快了,要么消费变慢了。
单位时间发送的消息增多,比如赶上大促或者抢购,短时间内不太可能优化消费端的代码来提升消费性能,唯一的办法是通过扩容消费端的实例数来提升总体的消费能力。严重影响 QM 甚至整个系统时,可以考虑临时启用多个消费者,并发接受消息,持久化之后再单独处理,或者直接丢弃消息,回头让生产者重新生产。
如果短时间内没有服务器资源扩容,没办法的办法是将系统降级,通过关闭某些不重要的业务,减少发送的数据量,最低限度让系统还能正常运转,服务重要业务。
监控发现,产生和消费消息的速度没什么变化,出现消息积压的情况,检查是有消费失败反复消费的情况。
监控发现,消费消息的速度变慢,检查消费实例,日志中是否有大量消费错误、消费线程是否死锁、是否卡在某些资源上    
8,如何保证消息的顺序性?
生产者保证消息入队的顺序。
MQ 本身是一种先进先出的数据接口,将同一类消息,发到同一个 queue 中,保证出队是有序的。
避免多消费者并发消费同一个 queue 中的消息。
9,如何保证消息不丢失?
生产者丢失消息:如网络传输中丢失消息、MQ 发生异常未成功接收消息等情况。 
解决办法:主流的 MQ 都有确认或事务机制,可以保证生产者将消息送达到 MQ。如 RabbitMQ 就有事务模式和 confirm 模式。
MQ 丢失消息:MQ 成功接收消息内部处理出错、宕机等情况。  
解决办法:开启 MQ 的持久化配置。
消费者丢失消息:采用消息自动确认模式,消费者取到消息未处理挂掉了。 
解决办法:改为手动确认模式,消费者成功消费消息再确认。

Redis

Mysql

高并发

分布式

设计模式

数据结构与算法

安全

Linux

HR非技术面

Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

简介

分享一些有关于 Java 体系的知识,包括Java 基础知识/数据结构/算法/面试技巧,高并发/高性能/分布式/微服务架构的原理,内容涵盖Java面试指南、Spring Boot、Dubbo、Zookeeper、Redis、Nginx、消息队列、系统设计、架构、编程规范等。 展开 收起
Java
Apache-2.0
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Java
1
https://gitee.com/shangxundev/java-interview-guide.git
git@gitee.com:shangxundev/java-interview-guide.git
shangxundev
java-interview-guide
java面试指南
master

搜索帮助