JAVA面试宝典2021版
一、Java 基础
1.JDK 和 JRE 有什么区别?
JRE( Java Runtime Environment)是Java 运行时环境……它是运行编译后的Java程序所必需的一切包,包括Java虚拟机(JVM)、Java基础类库、Java 命令和其他基础设施。但是,它不能用于创建新程序。
JDK是Java 开发工具包……功能齐全的SDKforJava。它拥有JRE所拥有的一切,还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具:jconsole,jvisualvm等工具软件,还包含了java程序编写所需的文档和demo例子程序。它能够创建和编译程序,是提供给程序员使用的。
2.== 和 equals 的区别是什么?
1、功能不同
"=="是判断两个变量或实例是不是指向同一个内存空间的值
"equals"是判断两个变量或实例所指向的内存空间的值是不是相同。
2、定义不同
"equals"在JAVA中是一个方法。
"=="在JAVA中只是一个运算符合
当使用==比较他们两个的值,比较的是他们的变量指向的地址值,除非这两个变量是同一个NEW出来的对象,则他们的
比较为true,反之为false
Equals()方法:
Java中所有的类都继承与基类Object,在Object中定义了equals方法,这个方法的初始行为是比较对象的内存地址。但是在一些类库中这个方法被重写了,如String、Integer、Date这些类中的equals方法有自身的实现,而不再是比较内存
地址值。
所有说对用复合型数据类型(引用类型),我们推荐使用重写了的equals方法进行比较,
3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗?
两个对象equals相等,则它们的hashcode必须相等,反之则不一定。两个对象==相等,则其hashcode一定相等, 反之不一定成立。
hashCode(哈希码值)的存在主要应用于查找的快捷性,如hashTable、hashMap等,hashCode码是确定对象在散列存储结构中的地址值。
如果两个对象通过equals方法比较相等,那么他们的hashCode值也一定相等。反之hashCode码相等,但是连个对象的equals比较不一定相等,也就是说这两个对象之间不一定适用于equals方法,只能说明这两个对象都存在于散列存储结构中。
4.final 在 java 中有什么作用?(在Java中final可以修饰类、变量、方法)
(1)Final修饰类:
当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
(2)Final修饰方法:当final修饰的方法后该方法不能被子类重写(可以重载多个final修饰的方法), 注意:重写的前提是子类可以从父类中继承此方法,如果父类中用final修饰的方法同时访问修饰符是private时,子类中则可以定义相同的方法名和参数
(3)Final修饰变量: final在修饰基本数据类型变量时,该变量一经赋值则不许改变,如果修饰的引用数据类型时,则代表着引用的地址值不能改变,但地址值所指向的内容可以改变。
①修饰成员变量时:修饰成员变量时必须显示初始化,初始化方式有两种,第一就是变量声名时直接初始化,二就是变量声明后不初始化,但必须要在这个变量所在的类的所有构造函数中对这个变量进行赋值。
②修饰局部变量时:当函数的参数通过final进行定义时,代表着该参数为只读,可以读取使用该参数,但不能修改该参数值。
5.java 中的 Math.round(-1.5) 等于多少?
Math的round方法是四舍五入,如果参数是负数,则往大的数如,Math.round(-1.5)=-1
6.String 属于基础的数据类型吗?(引用数据类型)
String类并不是基本数据类,而是一个类(class),是C++、java等编程语言中的字符串,
Java中八大基本类型数据:
1、字符类型: char 2、基本整型:byte,short,int,long 3、浮点型:float,double 4 布尔类型:boolean”
7.java中操作字符串都有哪些类?(String、StringBuffer、StringBuilder)它们之间有什么区别?
String : final修饰,String类的方法都是返回new String。即对String对象的任何改变都不影响到原对象,对字符串的修改操作都会生成新的对象。 StringBuffer : 对字符串的操作的方法都加了synchronized,保证线程安全。
StringBuilder : 不保证线程安全,在方法体内需要进行字符串的修改操作,可以new StringBuilder对象,调用 StringBuilder对象的append、replace、delete等方法修改字符串。
String底层初始化时默认char[]数组长度为0,StringBuffer、StringBuilder初始化默认的char[]数组长度为16
(因为String不可变,每次对String进行操作都会生成新的字符串)
中的方法则没有,代表着他是一个线程不安全的。所以在单线程的情况下选择StringBuilder要更快,然而在多线程当中
考虑使用StringBuffer更加安全些
方式进行声明那样就不会再堆内存中创建对象,此时String变量直接指向常量池,并且可以复用,效率更高
8.String str="i"与 String str=new String(“i”)一样吗?
String str = “i”代表着声明了一个变量str,此时在常量池中创建了一个内容为i的字符窜对象。
String str = new String(“I”);此时代表着创建了两个对象,str引用地址指向堆内存,如果常量池中没有字符窜i,则会
在常量池中创建第二个对象字符窜i。
9.如何将字符串反转?
1. 利用 StringBuffer 或 StringBuilder 的 reverse 成员方法:
public static String reverse(String str) {
return new StringBuilder(str).reverse().toString();
}
2. 利用 String 的 toCharArray 方法先将字符串转化为 char 类型数组,然后将各个字符进行重新拼接:
//自定义方法
public static String reverse(String str){
char[] chars = str.toCharArray();
//创建StringBuilder对象进行拼接
StringBuilder builder = new StringBuilder();
for (int i = chars.length - 1; i >= 0; i--) {
builder.append(chars[i]);
}
return builder.toString();
}
3. 利用 String 的 CharAt 方法取出字符串中的各个字符:
public static String reverse1(String str){
//创建StringBuilder对象进行拼接
StringBuilder builder = new StringBuilder();
//获取字符窜长度
int length = str.length();
for (int i = length-1; i >=0; i--) {
builder.append(str.charAt(i));
}
return builder.toString();
}
4.使用递归的方式进行反转
public static String reverse2(String str){
//获取字符窜长度
int length = str.length();
if(length<=1){
return str;
}
String left = str.substring(0, length/2);
String right = str.substring(length/2,length);
return reverse2(right)+reverse2(left);
}
10.String 类的常用方法都有那些?
indexOf(String str):查找指定的字符在当前字符窜第一次出现的索引值
charAt(int index) 返回指定索引处得字符
replace(char oldChar,char newChar): 它是通过用 newChar 替换此字符串中出现的所有 oldChar
trim() 去除字符串两端的空白
split() 分割字符串 返回分割后的字符串数组
getBytes() 返回字符串的byte类型数组
length() 返回字符串的长度
toLowerCase() 字符串转小写
toUpperCase() 字符串转大写
substring() 截取字符串
equals() 字符串比较
11.抽象类必须要有抽象方法吗?
抽象类可以没有抽象方法,但是如果你的一个类已经声明成了抽象类,即使这个类中没有抽象方法,它也不能再实例化,即不能直接构造一个该类的对象。 如果一个类中有了一个抽象方法,那么这个类必须声明为抽象类,否则编译通不过。
12.普通类和抽象类有哪些区别?
抽象类不能被实例化
抽象类可以有抽象方法,抽象方法只需申明,无需实现
含有抽象方法的类必须申明为抽象类
抽象类的子类必须实现抽象类中所有抽象方法,否则这个子类也是抽象类
抽象方法不能被声明为静态
抽象方法不能用 private 修饰
抽象方法不能用 final 修饰
13.抽象类能使用 final 修饰吗?
不能,抽象类是被用于继承的,final修饰代表不可修改、不可继承的。
14.接口和抽象类有什么区别?
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
7、抽象类里可以没有抽象方法
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。
15.java 中 IO 流分为几种?
Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的。
16.BIO、NIO、AIO 有什么区别?
BIO是一个连接一个线程。 NIO是一个请求一个线程。 AIO是一个有效请求一个线程。
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
17.Files的常用方法都有哪些?
Files.exists() 检测文件路径是否存在 Files.createFile()创建文件 Files.createDirectory()创建文件夹 Files.delete() 删除文件或者目录 Files.copy() 复制文件 Files.move() 移动文件 Files.size()查看文件个数 Files.read() 读取文件 Files.write()写入文件
二、容器
18.java 容器都有哪些?
List,Map,Set ,Collection ,List ,LinkedList ,ArrayList ,Vector ,Stack ,Set Map ,Hashtable ,HashMap ,WeakHashMap
数据容器主要分为了两类: Collection: 存放独立元素的序列。 Map:存放key-value型的元素对。(这对于需要利用key查找value的程序十分的重要!) 从类体系图中可以看出,Collection定义了Collection类型数据的最基本、最共性的功能接口,而List对该接口进行了拓展。 LinkedList :其数据结构采用的是链表,此种结构的优势是删除和添加的效率很高,但随机访问元素时效率较ArrayList类低。 ArrayList:其数据结构采用的是线性表,此种结构的优势是访问和查询十分方便,但添加和删除的时候效率很低。 HashSet: Set类不允许其中存在重复的元素(集),无法添加一个重复的元素(Set中已经存在)。HashSet利用Hash函数进行了查询效率上的优化,其contain()方法经常被
使用,以用于判断相关元素是否已经被添加过。 HashMap: 提供了key-value的键值对数据存储机制,可以十分方便的通过键值查找相应的元素,而且通过Hash散列机制,查找十分的方便。
19.Collection 和 Collections 有什么区别?
Collection 是集合的接口,其继承类又List Set
Collections 是集合的工具类,定义了许多操作集合的静态方法。是帮助类
20.List、Set、Map 之间的区别是什么?
List:有序集合
Set:不重复集合,LinkedHashSet按照插入排序,SortedSet可排序,HashSet无序
Map:键值对集合
(1)元素的重复性:
List集合中可以出现重复元素
Set集合中不可以出现重复元素,在向Set集合中存储多个重复的元素时会出现覆盖
Map集合采用key-value的形式存储,在Map中不能出现重复的Key键,但可以出现多个不同的Key对应相同的Value
(2)元素的有序性:
List集合及其所有的实现类都确保了元素的有序性
Set集合中的元素是无序的,但是某些Set集合通过特定的形式对其中的元素进行排序,如LinkedHashSet
Map和Set一样对元素进行了无序存储,如:TreeMap根据Key键实现了元素的升序排序
(3)元素是否为空值:
List集合中允许存在多个空值
Set最多允许一个空值出现
Map中只允许出现一个空键,但允许出现多个空值
21.HashMap 和 Hashtable 有什么区别?
1 HashMap不是线程安全的 hastmap是一个接口 是map接口的子接口,是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap允许null key和null value 2 HashTable是线程安全的一个Collection。 HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。 HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。 HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访
问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。 Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差异。
22.如何决定使用 HashMap 还是 TreeMap?
TreeMap
TreeMap的实现是基于红黑树结构。适用于按自然顺序或自定义顺序遍历键(key)。
HashMap
23.说一下 HashMap 的实现原理?
HashMap使用数组加链表实现。每个数组中储存着链表。当使用put方法储存key-value键值对时,会先调用key的hashCode方法,得到此key经特定哈希运算后的值,然后将此值通过其他运算(?)得到一个值,将这个值与(length-1)做或操作(&),相当于对数组长度做取余操作。最终得到一个值作为此key在数组中的索引值,然后将key-value键值对储存进去。通过这种方法将储存的不同key-value键值对“散列”到数组的不同位置。
在储存的时候,如果索引位置尚无元素,那么直接储存。如果有元素,那么就调用此key的equals方法与原有的元素的Key进行比较。如果返回true,说明在这个equals定义的规则上,这两个Key相同,那么将原有的key保留,用新的value代替原来的value。如果返回false,那么就说明这两个key在equals定义的规则下是不同元素,那么就与此链表的下一个结点进行比较,知道最后一个结点都没有相同元素,再下一个是null的时候,就用头插法将此key-value添加到链表上。
HashMap对重复元素的处理方法是:key不变,value覆盖。
当使用get方法获取key对应的value时,会和储存key-value时用同样的方法,得到key在数组中的索引值,如果此索引值上没有元素,就返回null。如果此索引值上有元素,那么就拿此key的equals方法与此位置元素上的key进行比较,如果返回true。就返回此位置元素对应的value。如果返回false,就一直按链表往下比较,如果都是返回false,那么就返回null。
另外:HashMap在JDK1.8之后引入红黑树结构。HashMap是线程不安全的,线程安全的是CurrentHashMap,不过此集合在多线程下效率低。
24.说一下 HashSet 的实现原理?
首先,我们需要知道它是Set的一个实现,所以保证了当中没有重复的元素。 一方面Set中最重要的一个操作就是查找。而且通常我们会选择HashSet使用的是散列函数,那么它当中的元素也就无序可寻。当中是允许元素为null的。
25.ArrayList 和 LinkedList 的区别是什么?
• 数据结构实现: ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。 • 随机访问效
率: ArrayList 比 LinkedList 在随机访问的时候效率要高, 因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。 • 增加和删除效率: 在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高, 因为 ArrayList 增删操作要影响数组内的其他数据的下标。 • 综合来说: 在需要频繁读取集合中的元素时,更推荐使用ArrayList, 而在插入和删除操作较多时,更推荐使用 LinkedList。
List转数组:toArray()方法
数组转List:Arrays的asList(a)方法
27.ArrayList 和 Vector 的区别是什么?
这两个类都实现了List接口(List接口继承了Collection接口),他们都是有序集合,即存储在这两个集合中的元素的位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引号取出某个元素,并且其中的数据是允许重复的,这是与HashSet之类的集合的最大不同处,HashSet之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。
ArrayList与Vector的区别主要包括两个方面:.
(1)同步性:
的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
(2)数据增长:
要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍) ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。
28.Array 和 ArrayList 有何区别?
①Array是Java中的数组,声明数组有三种方式 int[] a=new int[10]; int a[]=new int[10]; int a[]={1,2,3,4}; 可以看出:在定义一个数组的时候,必须指定这个数组的数据类型及数组的大小,也就是说数组中存放的元素个数固定并且类型一样
②ArrayList是动态数组,也就是数组的复杂版本,它可以动态的添加和删除元素,被称为”集合“,集合的声明如下ArrayList list = new ArrayList(10); ArrayList list1 = new ArrayList(); 可以看出:在不使用泛型的情况下,这个list是可以添加进不同类型的元素的,而且arraylist是可以不用指定长度的。在使用泛型时,我们就只能添加一种类型的数据了
29.在 Queue 中 poll()和 remove()有什么区别?
相同点:都是返回第一个元素,并在队列中删除返回的对象。
poll()会返回 null,而 remove()会直接抛出 NoSuchElementException 异常。
30.哪些集合类是线程安全的?
Vector:就比Arraylist多了个同步化机制(线程安全)。
Hashtable:就比Hashmap多了个线程安全。
ConcurrentHashMap:是一种高效但是线程安全的集合。
Stack:栈,也是线程安全的,继承于Vector。
31.迭代器 Iterator 是什么?
首先说一下迭代器模式,它是 Java 中常用的设计模式之一。用于顺序访问集合对象的元素,无需知道集合对象的底层实现。Iterator 是可以遍历集合的对象,为各种容器提供了公共的操作接口,隔离对容器的遍历操作和底层实现,从而解耦。
缺点是增加新的集合类需要对应增加新的迭代器类,迭代器类与集合类成对增加。
32.Iterator 怎么使用?有什么特点?
(1)Iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
(2)使用next()获得序 列中的下一个元素
(3)使用hasNext()检查序列中是否还有元素。
(4)使用remove()将迭代器新近返回的元素删除。
有什么特点:
(1) Iterator遍历集合元素的过程中不允许线程对集合元素进行修改,否则会抛出
ConcurrentModifificationEception的异常。
(2)Iterator遍历集合元素的过程中可以通过remove方法来移除集合中
的元素,删除的是上一次Iterator.next()方法返回的对象。
(3)Iterator必须依附于一个集合类对象而存在,Iterator本身不具有装载数据对象的功能。
(4)next()方法,该方法通过游标指向的形式返回Iterator下一个元
素。
33.Iterator 和 ListIterator 有什么区别?
(1)所属关系,ListIterator是一个Iterator的子类型。 (2)局限:只能应用于各种List类的访问。 (3)优势:
Iterator只能向前移动,而ListIterator可以双向移动。 (4)ListIterator 有 add() 方法,可以向 List 中添加对象,而
Iterator 不能。
34.怎么确保一个集合不能被修改?
我们可以采用Collections包下的unmodifiableMap方法,通过这个方法返回的map,是不可以修改的。他会报
java.lang.UnsupportedOperationException错。
同理:Collections包也提供了对list和set集合的方法。 Collections.unmodififiableList(List)
Collections.unmodififiableSet(Set)
三、多线程
35.并行和并发有什么区别?
解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
解释二: 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
解释三:在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群 所以并发编程的目标是充分的利用处理器的每一个核,以
达到最高的处理性能。
36.线程和进程的区别?
进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫轻量级进程。
线程的划分小于进程,线程是隶属于某个进程的。进程是程序的一种动态形式,是CPU,内存等资源占用的基本单
位,而线程是不能占有这些资源的。
进程之间相互独立,通信比较困难,而线程之间共享一块内存区域,通信比较方便。
进程在执行过程中包含比较固定的入口,执行顺序,出口,而线程的这些过程会被应用程序所控制。
37.守护线程是什么?
1、守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完
毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。
2、再换一种说法,如果有用户自定义线程存在的话,jvm就不会退出——此时,守护线程也不能退出,也就是它还
要运行,干嘛呢,就是为了执行垃圾回收的任务啊。
38.创建线程有哪几种方式?
1,继承Thread类,重写run方法;
2,实现Runnable接口,重写run方法,但是比继承Thread类好用,实现接口还 可以继承类,避免了单继承带来的局限性; 3,使用Executor框架创建线程池。Executor框架是juc里提供的线程池 的实现。 调用线程的start():启动此线程;调用相应的run()方法
Thread的常用方法:
1.start():启动线程并执行相应的run()方法
2.run():子线程要执行的代码放入run()方法中
3.currentThread():静态的,调取当前的线程
4.getName():获取此线程的名字
5.setName():设置此线程的名字
6.yield():调用此方法的线程释放当前CPU的执行权(很可能自己再次抢到资源)
7.join():在A线程中调用B线程的join()
方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕, A线程再接着join()之后的代码执行
8.isAlive():判断当前线程是否还存活
9.sleep(long l):显式的让当前线程睡眠l毫秒 (只能捕获异常,因为父类run方法没
有抛异常)
10.线程通信(方法在Object类中):wait() notify() notifyAll() *设置线程的优先级(非绝对,只是相对几
率大些)
11.getPriority():返回线程优先值 setPriority(int newPriority):改变线程的优先级
39.说一下 runnable 和 callable 有什么区别?
、相同点:
1. 两者都是接口;(废话)
2. 两者都可用来编写多线程程序;
3. 两者都需要调用Thread.start()启动线程
不同点:
1. 两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返
回结果;
40.线程有哪些状态?
线程状态有 5 种,新建,就绪,运行,阻塞,死亡
新建状态:创建一个新的线程对象
就绪:新的线程对象调用start()方法,该状态下的线程位于可运行的线程池中,等待获取CPU调度时间
运行:线程池中的线程获取到了CPU的资源,开始执行程序
阻塞: 指运行状态下的线程因为某种原因放弃了CPU使用权,暂时停止运行,直到线程再次进入就绪状态才有机会获取CPU的
资源进入到运行状态
(1)、等待阻塞:运行中的线程执行了wait()方法,JVM会把该线程放入等待队列(等待池)中
(2)、同步阻塞:运行中的线程在获取对象的同步锁时,若当前的对象锁被其他线程所占有,则JVM会将该线程放入锁池中
(3)、其他阻塞:运行中的线程执行了Thread.sleep()方法 或者join()方法,或者发送了I/O请求时,JVM会将该线程设置为
阻塞状态
死亡:线程run(),main()方法执行结束,或值run()执行过程中出现异常,则该线程结束生命周期
1. 线程 start 方法执行后,并不表示该线程运行了,而是进入就绪状态,意思是随时准备运行,但是真正何时运
行,是由操作系统决定的,代码并不能控制,
2. 同样的,从运行状态的线程,也可能由于失去了 CPU 资源,回到就绪状态,也是由操作系统决定的。这一步
中,也可以由程序主动失去 CPU 资源,只需调用 yield 方法。
3. 线程运行完毕,或者运行了一半异常了,或者主动调用线程的 stop 方法,那么就进入死亡。死亡的线程不可逆
转。
4. 下面几个行为,会引起线程阻塞。
主动调用 sleep 方法。时间到了会进入就绪状态 主动调用 suspend 方法。主动调用 resume 方法,会进入就绪状态
调用了阻塞式 IO 方法。调用完成后,会进入就绪状态。 试图获取锁。成功的获取锁之后,会进入就绪状态。 线程在
等待某个通知。其它线程发出通知后,会进入就绪状态
41.sleep() 和 wait() 有什么区别?
1、同步锁zd的对待不同:
sleep()后,程序并不会不释放同步锁。
wait()后,程序会释放同步锁。
2、用法的不同:
sleep()可以用时间指定版来使他自动醒过来。如果时间不到你只能调用interreput()来强行打断。
wait()可以用notify()直接唤起。
(1)sleep()方法是Thread类中的静态方法,wait属于Object基类的成员方法(他们都会是线程进入到阻塞状态)
(2)sleep()方法是线程类(Thread)的方法,不会涉及到线程通信,调用sleep()方法后会使线程睡眠指定的时间,但此时线程不会释放同步锁。wait()涉及到线程通信问题,在调用wait方法后线程会主动释放同步锁.进入等待队列,可以通过notify/notifyAll唤醒线程。才会进入锁池中准备获取对象锁
(3)对象的wait方法即notify()、notifyAll()只能在同步代码块中执行,sleep()能随处调用
(4)sleep()方法必须捕获异常(InterruptedException), wait方法即notify()、notifyAll()则不需要
42.notify()和 notifyAll()有什么区别?
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法
(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该
对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的 wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒
的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由
等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争 优先级高的线程竞争到对象
锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到
等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这
时锁池中的线程会继续竞争该对象锁。
43.线程的 run()和 start()有什么区别?
调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多
线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。 一个线程对线的 start() 方法只能调用一次,多次调
用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。
44.创建线程池有哪几种方式?
1、newCachedThreadPool(),它是用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存
线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置时间超过60秒,则被终止并移除缓存;长
时间闲置时,这种线程池,不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。
2、newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队
列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动线程数目,将在工作队列
中等待空闲线程出现;如果工作线程退出,将会有新的工作线程被创建,以补足指定数目nThreads。
3、newSingleThreadExecutor(),它的特点在于工作线程数目限制为1,操作一个无界的工作队列,所以它保证了所
有的任务都是被顺序执行,最多会有一个任务处于活动状态,并且不予许使用者改动线程池实例,因此可以避免改变
线程数目。
4、newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个
ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
5、newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部
会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
45.线程池都有哪些状态?
\1. RUNNING:线程池一旦被创建,就处于 RUNNING 状态,任务数为 0,能够接收新任务,对已排队的任务进行处
理。
\2. SHUTDOWN:不接收新任务,但能处理已排队的任务。调用线程池的 shutdown() 方法,线程池由 RUNNING 转
变为 SHUTDOWN 状态。
\3. STOP:不接收新任务,不处理已排队的任务,并且会中断正在处理的任务。调用线程池的 shutdownNow() 方
法,线程池由(RUNNING 或 SHUTDOWN ) 转变为 STOP 状态。
\4. TIDYING:
SHUTDOWN 状态下,任务数为 0, 其他所有任务已终止,线程池会变为 TIDYING 状态,会执行 terminated() 方
法。线程池中的 terminated() 方法是空实现,可以重写该方法进行相应的处理。
线程池在 SHUTDOWN 状态,任务队列为空且执行中任务为空,线程池就会由 SHUTDOWN 转变为 TIDYING 状态。
线程池在 STOP 状态,线程池中执行中任务为空时,就会由 STOP 转变为 TIDYING 状态。
\5. TERMINATED:线程池彻底终止。线程池在 TIDYING 状态执行完 terminated() 方法就会由 TIDYING 转变为
TERMINATED 状态。
46.线程池中 submit()和 execute()方法有什么区别?
submit(Callable task)、submit(Runnable task, T result)、submit(Runnable task)归属于ExecutorService接口。
execute(Runnable command)归属于Executor接口。ExecutorService继承了Executor。
47.在 java 程序中怎么保证多线程的运行安全?
线程的安全性问题体现在:
原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性 可见性:一个线程对共享变量的修改,另外一个线
程能够立刻看到 有序性:程序执行的顺序按照代码的先后顺序执行
导致原因:
缓存导致的可见性问题 线程切换带来的原子性问题 编译优化带来的有序性问题解决办法:
JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题 synchronized、volatile、LOCK,可以解决
可见性问题 Happens-Before 规则可以解决有序性问题
48.多线程锁的升级原理是什么?
锁的级别从低到高:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
锁分级别原因:
没有优化以前,synchronized是重量级锁(悲观锁),使用 wait 和 notify、notifyAll 来切换线程状态非常消耗系统
资源;线程的挂起和唤醒间隔很短暂,这样很浪费资源,影响性能。所以 JVM 对 synchronized 关键字进行了优化,
把锁分为 无锁、偏向锁、轻量级锁、重量级锁 状态。
无锁:没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失
败的线程会不断重试直到修改成功。
偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带
来的性能开销。偏向锁,指的就是偏向第一个加锁线程,该线程是不会主动释放偏向锁的,只有当其他线程尝试竞争
偏向锁才会被释放。
偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被
锁定状态。如果线程不处于活动状态,则将对象头设置成无锁状态,并撤销偏向锁;
如果线程处于活动状态,升级为轻量级锁的状态。
轻量级锁:轻量级锁是指当锁是偏向锁的时候,被第二个线程 B 所访问,此时偏向锁就会升级为轻量级锁,线程 B
会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。
当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级
锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。
重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实
现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。
49.什么是死锁?
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力
作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进
程。”
50.怎么防止死锁?
尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时
间,超时可以退出防止死锁。 尽量使用 Java. util. concurrent 并发类代替自己手写锁。 尽量降低锁的使用粒度,尽
量不要几个功能用同一把锁。 尽量减少同步的代码块。
51.ThreadLocal 是什么?有哪些使用场景?
Thread Local类是线程局部变量,是一种实现线程安全的方式。但是在管理环境下使用线程局部变量的时候要特别小
心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没
有释放,Java应用就存在泄露大的风险
52.说一下 synchronized 底层实现原理?
可以保证方法或代码块在运行时,同一时刻只有一个方法可以进入临时界区,同时它还可以保证共享变量的内存可见
性
普通同步方法,锁是当前实列对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的 对象
53.synchronized 和 volatile 的区别是什么?
volatile本质是告诉jvm当前变量寄存器中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变
量,只有当前线程可以访问该变量,其他线程被阻塞。
volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改和可见性
volatile不会造成线程阻塞;synchronized可能会造成线程阻塞。
volatile标记的变量不会被编译器优化;synchronized可能被编译器优化
54.synchronized 和 Lock 有什么区别?
首先synchronized是Java内置关键字,在jvm层里面,LOck是个类;
synchronized无法判断是否获取锁的状态,lock可以判断是否获取到锁
synchronized会自动释放锁,否则容易造成线程死锁
synchronized会等待线程,Lock 锁不会等待如果尝试获取不到锁,线程可以不用一直等待就结束了
synchronized锁适合代码少量的同步问题,lock锁适合大量的代码同步问题
55.synchronized 和 ReentrantLock 区别是什么?
一个是关键字,一个是类,更灵活的特性,可被继承可以有方法
ReentrantLock可以获取锁的等待时间设置,这样避免死锁,各种锁的信息,灵活实现多路通知
56.说一下 atomic 的原理?
当多个线程同时对该变量进行更新时,仅有一个线程能成功,而未成功的线程可以向自旋锁一样,继续尝试,一直等
待到执行成功
四、反射
57.什么是反射?
主要指程序可以访问,检测,修改本生状态或行为的一种能力
Java运行是环境中,对任意一个类,是否知道这个类有属性和方法?
58.什么是 java 序列化?什么情况下需要序列化?
序列化:将 Java 对象转换成字节流的过程。
反序列化:将字节流转换成 Java 对象的过程。当 Java 对象需要在网络上传输 或者 持久化存储到文件中时,就需要对 Java 对象进行序列化处理。
序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉
jvm 这个类的对象可以被序列化。
59.动态代理是什么?有哪些应用?
当想要给实现某个接口的类中的方法,加一些格外的处理。比如加日志,加事务,加权限等。可以给这个类创建一个
代理,这个代理并不是定义好的,是动态生成的,具有解耦,灵活,扩展性强
60.怎么实现动态代理?
首先必须定义一个接口,还要有一个InvocationHandler处理类,再有一个工具类Proxy。利用到InvocationHandler
处理类,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实列化产生代理对象,最后返
回
五、对象拷贝
61.为什么要使用克隆?
想对一个对象进行处理,又想保留原有的数据进行接下来的操作,就需要克隆了。克隆分浅克隆和深克隆,浅克隆后
的对象中非基本对象和原对象指向同一块内存,因此对这些非基本对象的修改会同时更改克隆前后的对象。深克隆可
以实现完全的克隆,可以用反射的方式或序列化的方式实现。
62.如何实现对象克隆?
1实现Cloneable接口并重写object类中的clone()方法
2实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
63.深拷贝和浅拷贝区别是什么?
浅拷贝只是复制了对象的引用地址,两个对象指向同一内存地址,所有修复其中的任意值,另一个值都会随之
变化
深拷贝是将对象及值复制过来,两个对象修改其中任意值另一个值不会改变
六、Java Web
64.jsp 和 servlet 有什么区别?
jsp编译后变成了servlet
jsp更擅长变现页面显示,servlet更擅长逻辑控制
servlet没有内置对象,jsp的内置对象都是必须通过httpservletrequst对象,httpservletrespose对象以及
httpservlet对象得到的
jsp是servlet的一种简化
65.jsp 有哪些内置对象?作用分别是什么?
request:封装客户请求get或post
response:封装服务器对客户端的响应
pageContext:通过对该对象获取其他对象
session:封装用户会话的对象
application:封装服务器运行环境的对象out:输出服务器响应的输出流对象
confifig:web应用的配置对象
page:jsp页面本身
exception:封装页面抛出异常的对象
66.说一下 jsp 的 4 种作用域?
page:jsp页面本身
request:封装客户请求get或post
response:封装服务器对客户端的响应
application:封装服务器运行环境的对象
67.session 和 cookie 有什么区别?
由于HTTP协议是无状态的协议,所以服务端余姚记录用户的状态session是保存在服务端的
cookie是保存在客户端的
68.说一下 session 的工作原理?
session是一个存在服务器上的类似于一个散列表格的文件,里面存有我们需要信息,需要用的时候取出来,类似于
大号的map,里面的键存储的是用户的sessionid,用户向服务器发送请求的时候会带上这个sessionid。就可以从中
取值
69.如果客户端禁止 cookie 能实现 session 还能用吗?
不能的到session,禁用相当于失去session ,一般认为两个是独立的东西,cookie 采用的是客户端保持状态方案,
session 采用的是客户端保持方案
70.spring mvc 和 struts 的区别是什么?
struts2是类级别的拦截器,struts2中Action的一个方法可以对应一个url,而其类属性却被所有方法共享,这也
就无法用注解或其他的方式标识其所属方法了,只能设计为多例
springmvc是方法级别的拦截,在spring整合时,springmvc的controller Bean默认单列模式Singleton,所以
默认对所有的请求,只会创建一个controller
71.如何避免 sql 注入?
PreparedStatement
使用正则表达式过滤传入的参数
字符串过滤
jsp页面判断代码
72.什么是 XSS 攻击,如何避免?
跨站脚本攻击,攻击者向有xss漏洞的网站中输入恶意的HTML代码,当用户浏览该网站时,这段HTML就会自
动执行,类似于sql注入攻击,而xss攻击中,通过插入恶意脚本,实现对用户浏览器的控制,获取用户的一些信
息。
总体思路:对输入url进行过滤,对输出进行编码。
73.什么是 CSRF 攻击,如何避免?跨站请求伪造,攻击者伪造用户的浏览器的请求,向访问一个用户自己曾认证访问的网站发出来,使目标网站接收并
误以为是用户的真是操作而执行的命令,常用于盗取 账号,转账,发送虚假信息等。攻击者利用请求网站的验证漏
洞。网站不能验证请求是否源于用户的真实信息。
七、异常
74.throw 和 throws 的区别?
throw:
表示方法内抛出某种异常对象 如果异常对象是非 RuntimeException 则需要在方法申明时加上该异常的抛出 即需要
加上 throws 语句 或者 在方法体内 try catch 处理该异常,否则编译报错 执行到 throw 语句则后面的语句块不再执
行 throws:
方法的定义上使用 throws 表示这个方法可能抛出某种异常 需要由方法的调用者进行异常处理
75.fifinal、fifinally、fifinalize 有什么区别?
1.fifinal : 1、修饰符(关键字) 如果一个类被声明为fifinal,意味着它不能再派生新的子类,不能作为父类被继承。因此一个
类不能及被声明为abstract,又被声明为fifinal的。 2、将变量或方法声明为...
2.fifinally: 在异常处理时提供fifinally块来执行清楚操作。如果抛出一个异常,那么相匹配的catch语句就会执行,然后控
制...
3.fifinalize: 是方法名。java技术允许使用fifinalize()方法在垃圾收集器将对象从内存中清除之前做必要的清理工作。这
个方法是在垃圾收集器在确定了,被清理对象没有被引用的情况下调用的。.
76.try-catch-fifinally 中哪个部分可以省略?
catch 和 fifinally 语句块可以省略其中一个。
77.try-catch-fifinally 中,如果 catch 中 return 了,fifinally 还会执行吗?
会执行,在return 前执行
78.常见的异常类有哪些?
Error,即错误,代表JVM本身的错误,处理程序运行环境方面的异常,不能通过代码处理。比如
OutOfMemoryError,AWTError等。
Exception:即异常,程序运行时发生,可以被java异常处理机制使用。比如IOException,SQLEXception,
RuntimeException等等。
以上,Error,RuntimeException是非检查异常
八、网络
79.http 响应码 301 和 302 代表的是什么?有什么区别?
301 redirect: 301 代表永久性转移(Permanently Moved)
302 redirect: 302 代表暂时性转移(Temporarily Moved )
301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的
网址交换为重定向之后的网址;302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地
址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。80.forward 和 redirect 的区别?
1、请求方不同
redirect:客户端发起的请求
forward:服务端发起的请求
2、浏览器地址表现不同
redirect:浏览器地址显示被请求的
urlforward:浏览器地址不显示被请求的url
3、参数传递不同
redirect:重新开始一个request,原页面的request生命周期结束。
forward:forward另一个连接的时候。request变量是在其生命周期内的。另一个页面也可以使用,其实质是
把目标地址include。
81.简述 tcp 和 udp的区别?
UDP 是面向无连接的通讯协议,UDP 数据包括目的端口号和源端信息。 优点:UDP 速度快、操作简单、要求
系统资源较少,由于通讯不需要连接,可以实现广播发送 缺点:UDP 传送数据前并不与对方建立连接,对接收
到的数据也不发送确认信号,发送端不知道数据是否会正确接收,也不重复发送,不可靠。 TCP 是面向连接的
通讯协议,通过三次握手建立连接,通讯完成时四次挥手 优点:TCP 在数据传递时,有确认、窗口、重传、阻
塞等控制机制,能保证数据正确性,较为可靠。 缺点:TCP 相对于 UDP 速度慢一点,要求系统资源较多。
82.tcp 为什么要三次握手,两次不行吗?为什么?
两次握手只能保证单向连接是畅通的。
只有经过第三次握手,才能确保双向都可以接收到对方的发送的 数据。
83.说一下 tcp 粘包是怎么产生的?
tcp 粘包可能发生在发送端或者接收端,分别来看两端各种产生粘包的原因:
发送端粘包:发送端需要等缓冲区满才发送出去,造成粘包; 接收方粘包:接收方不及时接收缓冲区的包,造成多
个包接收。
84.OSI 的七层模型都有哪些?
1、物理层协议有:EIA/TIA-232, EIA/TIA-499,V.35, V.24,RJ45, Ethernet, 802.3
2、数据链路层协议有:Frame Relay,HDLC,PPP, IEEE 802.3/802.2
3、网络层协议有:IP,IPX,AppleTalk DDP
4、传输层协议有:TCP,UDP,SPX
5、会话层协议有:RPC,SQL,NFS,NetBIOS,names,AppleTalk
6、表示层协议有:TIFF,GIF,JPEG,PICT,ASCII,EBCDIC,encryption
7、应用层协议有:FTP,WWW,Telnet,NFS,SMTP,Gateway,SNMP
85.get 和 post 请求有哪些区别?
1.get是从服务器上获取数据,post是向服务器传送数据。
2.get请求时通过URL直接请求数据,数据信息可以在URL中直接看到,比如浏览器访问;而post请求是放在请求头中
的,用户无法直接看到。
3.get传送的数据量较小,有限制,不能大于2KB;这主要是因为它受约于URL长度的限制。p...
4.get请求因为数据参数是暴露在URL中的,所以安全性比较低,如密码不能暴露的就不能用get
86.如何实现跨域?1、jsonp 利用了 script 不受同源策略的限制 缺点:只能 get 方式,易受到 XSS攻击
2、CORS(Cross-Origin Resource Sharing),跨域资源共享 当使用XMLHttpRequest发送请求时,如果浏览器
发现违反了同源策略就会自动加上一个请求头 origin; 后端在接受到请求后确定响应后会在后端在接受到请求
后确定响应后会在 Response Headers 中加入一个属性 Access-Control-Allow-Origin; 浏览器判断响应中的
Access-Control-Allow-Origin 值是否和当前的地址相同,匹配成功后才继续响应处理,否则报错 缺点:忽略
cookie,浏览器版本有一定要求
3、代理跨域请求 前端向发送请求,经过代理,请求需要的服务器资源 缺点:需要额外的代理服务器
4、Html5 postMessage 方法 允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本、多窗
口、跨域消息传递 缺点:浏览器版本要求,部分浏览器要配置放开跨域限制
5、修改 document.domain 跨子域 相同主域名下的不同子域名资源,设置 document.domain 为 相同的一级
域名 缺点:同一一级域名;相同协议;相同端口
6、基于 Html5 websocket 协议 websocket 是 Html5 一种新的协议,基于该协议可以做到浏览器与服务器全
双工通信,允许跨域请求 缺点:浏览器一定版本要求,服务器需要支持 websocket 协议
7、document.xxx + iframe 通过 iframe 是浏览器非同源标签,加载内容中转,传到当前页面的属性中 缺点:
页面的属性值有大小限制
87.说一下 JSONP 实现原理?
首先在客户端注册一个callback,然后把callback的名字传给服务器。此时,服务器先生成json数据,然后以
javascript语法的方式,生成function,function名字就是传递上来I带参数jsonp。最后将json数据直接以入参
的方式,放置function中,这样就生成js语法的文档,返回给客户端。客户端浏览器,解析script变迁,并执行
返回javascript文档,此时数据作为参数,传入了客户端预先定义好的callback函数里。简单的说,就是利用
script标签没有跨域限制的“漏洞”来达到与第三方通讯的目的。
九、设计模式
88.说一下你熟悉的设计模式?
Java中一般认为有23 种设计模式, 总体来说设计模式分为三大类:
创建型模式,共五种: 工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式,共七
种: 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 行为型模式,共十一
种: 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录 模式、状态模式、
访问者模式、中介者模式、解释器模式。
// 直接创建对象 public static Singleton instance = new Singleton(); // 私有化构造函数 private Singleton() { }
// 返回对象实例 public static Singleton getInstance() { return instance; }
89.简单工厂和抽象工厂有什么区别?
简单工厂模式 是由一个工厂对象创建产品实例,简单工厂模式的工厂类一般是使用静态方法,通过不同的参数的创
建不同的对象实例 可以生产结构中的任意产品,不能增加新的产品
抽象工厂模式 提供一个创建一系列相关或相互依赖对象的接口,而无需制定他们具体的类,生产多个系列产品 生产
不同产品族的全部产品,不能新增产品,可以新增产品族
十、Spring/Spring MVC
90.为什么要使用 spring?
1.方便解耦,便于开发(Spring就是一个大工厂,可以将所有对象的创建和依赖关系维护都交给spring管理)
2.spring支持aop编程(spring提供面向切面编程,可以很方便的实现对程序进行权限拦截和运行监控等功能)3.声明式事务的支持(通过配置就完成对事务的支持,不需要手动编程)
4.方便程序的测试,spring 对junit4支持,可以通过注解方便的测试spring 程序
5.方便集成各种优秀的框架()
6.降低javaEE API的使用难度(Spring 对javaEE开发中非常难用的一些API 例如JDBC,javaMail,远程调用等,都
提供了封装,是这些API应用难度大大降低)
91.解释一下什么是 aop?
在业务系统中,总有一些不得不处理的事情,我们 将这些重复性的代码抽取出来,放在专门的类中, 在通过spring
的AOP的核心对代码段进行增强处理。 在不改变原代码的基础上进行功能增强。有五种 增强方式,前置增强,后置
增强,环绕增强,引介增强。异常增强。
92.解释一下什么是 ioc?
ioc(inverse of control )控制反转:所谓控制反转就是把对象(bean)对象和维护对象(bean)之间的关系
的权利转移到Sqring容器中去了(ApplicationContext.xml)而程序本身不在维护了
di(dependency injection)依赖注入:实际上DI和IOC是同一个概念,因为在ApplicationContext.xml配置文
件中bean和bean之间通过ref来维护的时候是相互依赖的,所以又叫做依赖注入。也就是控制反转
93.spring 有哪些主要模块?
Spring有七大功能模块,分别是Spring Core,AOP,ORM,DAO,MVC,WEB,Context。 1,Spring Core
Core模块是Spring的核心类库,Spring的所有功能都依赖于该类库,Core主要实现IOC功能,Sprign的所有功
能都是借助IOC实现的。 2,AOP AOP模块是Spring的AOP库,提供了AOP(拦截器)机制,并提供常用的拦截
器,供用户自定义和配置。 3,ORM Spring 的ORM模块提供对常用的ORM框架的管理和辅助支持,Spring支
持常用的Hibernate,ibtas,jdao等框架的支持,Spring本身并不对ORM进行实现,仅对常见的ORM框架进行
封装,并对其进行管理 4,DAO模块 Spring 提供对JDBC的支持,对JDBC进行封装,允许JDBC使用Spring资
源,并能统一管理JDBC事物,并不对JDBC进行实现。(执行sql语句) 5,WEB模块 WEB模块提供对常见框架
如Struts1,WEBWORK(Struts 2),JSF的支持,Spring能够管理这些框架,将Spring的资源注入给框架,也
能在这些框架的前后插入拦截器。 6,Context模块 Context模块提供框架式的Bean访问方式,其他程序可以通
过Context访问Spring的Bean资源,相当于资源注入。 7,MVC模块 WEB MVC模块为Spring提供了一套轻量级
的MVC实现,在Spring的开发中,我们既可以用Struts也可以用Spring自己的MVC框架,相对于Struts,Spring
自己的MVC框架更加简洁和方便。
94.spring 常用的注入方式有哪些?
构造方法注入,setter 注入,基于注解的注入
主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
@Component:可以用于注册所有bean @Repository:主要用于注册dao层的bean @Controller:主要用于注册控
制层的bean @Service:主要用于注册服务层的bean
95.spring 中的 bean 是线程安全的吗?
容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体
还是要结合具体scope的Bean去研究。
96.spring 支持几种 bean 的作用域?
singleton:单例模式,在整个Spring IoC容器中,使用 singleton 定义的 bean 只有一个实例
prototype:原型模式,每次通过容器的getbean方法获取 prototype 定义的 bean 时,都产生一个新的 bean
实例97.spring 自动装配 bean 有哪些方式?
(1)在XML中进行显式配置 (2)在java中进行显式配置 (3)隐式的bean发现和自动装配(一般推荐使用自动装
配bean的方式)
98.spring 事务实现方式有哪些?
(1)编程式事务管理对基于 POJO 的应用来说是唯一选择。我们需要在代码中调用beginTransaction()、
commit()、rollback()等事务管理相关的方法,这就是编程式事务管理。 (2)基于 TransactionProxyFactoryBean
的声明式事务管理 (3)基于 @Transactional 的声明式事务管理 (4)基于Aspectj AOP配置事务
99.说一下 spring 的事务隔离?
READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更数据,会出现脏读、不可重
复读和幻读问题。 READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更数据,
可避免脏读,仍会出现不可重复读和幻读问题。 REPEATABLE READ(可重复读):确保事务可以多次从一个字
段中读取相同的值,在此事务持续期间,禁止其他事务对此字段的更新,可以避免脏读和不可重复读,仍会出
现幻读问题。 SERIALIZABLE(序列化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止
其他事务对该表执行插入、更新和删除操作,可避免所有并发问题,但性能非常低。 Spring隔离级别
100.说一下 spring mvc 运行流程?
用户发送请求到前端控制器DispatcherServlet,之后调用HanderMapping处理器找到具体的处理器返回,再调用
HanderlerAdapter处理适配器,再调用具体的Controller,执行完后返回Modeandview,handlerAdate将结果返回
给DispatcherServlet,再传给ViewResover视图解析器解析后返回View,DispatcherServlet渲染后响应
101.spring mvc 有哪些组件?
HanderMapp处理请求,HanderAdapter适配器,ViewResolver视图解析,Contoller处理器,DispatcherServlet
102.@RequestMapping 的作用是什么?
处理请求地址映射的注解,如果用于类上,表示类中的所有响应请求方法都是以该路径作为父路径
103.@Autowired 的作用是什么?
对l类成员变量、方法和构造函数进行标注,完成自动装配的工作
十一、Spring Boot/Spring Cloud
104.什么是 spring boot?
springboot是spring下的子项目,是spring的一站式解决方案,主要是简化spring的使用难度,降低对配置文件的要
求,使开发人员跟容易上手
105.为什么要用 spring boot?
1.简化spring配置文件 2.没有代码和xml文件的生成 3.内置Tomcat 4.能够独立运行 5.简化监控
106.spring boot 核心配置文件是什么?
核心配置文件有application和bootstarp配置文件
107.spring boot 配置文件有哪几种类型?它们有什么区别?两种,application主要作用于springboot 自动化配置文件
bootstarp:使用Spring Cloud Confifig注册中心时 需要在bootStarp配置文件中添加链接到配置中心的配置属性来加
载外部配置中心的配置信息。一些固定的不能被覆盖的属性。一些加密/解密的场景
108.spring boot 有哪些方式可以实现热部署?
模板引擎热部署,spring Loader热部署,JRebel热部署工具
109.jpa 和 hibernate 有什么区别?
主要区别是jpa是一个规范,hibernate是Red hat对jap规范的实现
110.什么是 spring cloud?
为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,领导
选举,分布式会话,集群状态)。
111.spring cloud 断路器的作用是什么?
当一个服务调用另一个服务由于网络原因或自身原因出现问题,调用者就会等待被调用者的响应 当更多的服务请求
到这些资源导致更多的请求等待,发生连锁效应(雪崩效应)
112.spring cloud 的核心组件有哪些?
服务发现Netflflix Eureka,客户端负载均衡Netflflix Ribbon,断路器Netflflix Hystrix,服务网关Netflflix Zuul,分布式配
置Spring cloud confifig
十三、Mybatis
125.mybatis 中 #{}和 ${}的区别是什么?
占位符和拼接符
126.mybatis 有几种分页方式?
数组分页,SQL分页,拦截器分页,RowBounds分页
127.RowBounds 是一次性查询全部结果吗?为什么?
如果不使用offffset和limit,则返回该条件下的所有,如果使用则返回设置的
128.mybatis 逻辑分页和物理分页的区别是什么?
物理分页速度上并不一定快于逻辑分页,
逻辑分页速度上也并不一定快于物理分页。
物理分页总是优于逻辑分页:没有必要将属于数据库端的压力加诸到应用端来,
就算速度上存在优势,然而其它性能上的优点足以弥补这个缺点
129.mybatis 是否支持延迟加载?延迟加载的原理是什么?
支持,原理是使用CGLIDB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法
130.说一下 mybatis 的一级缓存和二级缓存?
一级缓存是Sqlsession级别的缓存,也就是同一个Sqlsession内执行两次或多次相同结果的查询语句,只会再第一次
发送sql语句查询
二级缓存默认是不开启的,是Mapper级别的缓存,是多个Sqlsession之间可以共享数据
131.mybatis 和 hibernate 的区别有哪些?
mybatis和hibernate不同,它不完全是一个ORM框架,因为Mybatis需要程序员自己编写sql
mybatis直接编写原生态sql,可以严格控制sql执行性能,灵活度高,但是灵活的前提是mybatis无法做到数据库无关
性
hibernate对象/关系映射能力强,数据库无关性好
132.mybatis 有哪些执行器(Executor)?
SImpleExecutor:每次执行一个update或select,就开启一个statment对象,用完就关闭
ReuseExecutor:执行update或select,以sql作为key查找statment对象,存在就是用,不存在就创建,用完后不关
闭
BatchExecutor:执行update,将所有sql都添加到批处理中,等待统一执行
133.mybatis 分页插件的实现原理是什么?
使用mybatis提供的插件接口,实现自定义插件,在插件拦截器方法内拦截器待执行的sql,然后再重写sql,添加物
理分页语句和物理分页参数
134.mybatis 如何编写一个自定义插件?
1. 编写Interceptor的实现类
2. 使用@Intercepts注解完成插件签名 说明插件的拦截四大对象之一的哪一个对象的哪一个方法
3. 将写好的插件注册到全局配置文件中
十四、RabbitMQ
135.rabbitmq 的使用场景有哪些?
跨系统的异步通信
多个应用之间的耦合
应用内的同步变异步
消息驱动的架构
136.rabbitmq 有哪些重要的角色?
生产者,消费者,代理137.rabbitmq 有哪些重要的组件?
ConnectionFactory(连接管理器)
Channel(信道)
Exchange(交换器)
Queue(队列)
RoutingKey(路由键)
BindingKey(绑定键)
138.rabbitmq 中 vhost 的作用是什么?
虚拟的broker,最重要的是拥有独立的权限系统,可以做到vhost范围的用户控制
139.rabbitmq 的消息是怎么发送的?
必须连接到RabbitMQ服务器才能发布和消费消息,它们之间会创建一个tcp连接,一旦tcp打开并通过了认证,就会
创建一条amqp信道,信道是创建在“真实”的tcp上的蓄奴链接,amqp命令都是通过信道发送出去
140.rabbitmq 怎么保证消息的稳定性?
提供事务的功能,通过将channel设置为confifirm模式
141.rabbitmq 怎么避免消息丢失?
消息持久化,ack确认机制,设置集群镜像模式,消息补偿机制
142.要保证消息持久化成功的条件有哪些?
声明队列必须设置持久化durable设置为rue
消息推送投递模式必须设置持久化,deliverymode设置为2
消息已经到达持久化交换器
消息已经到达持久化队列
以上都满足才能保证消息持久化成功
143.rabbitmq 持久化有什么缺点?
吃计划的缺点就是降低了服务器的吞吐量
因为使用的是磁盘而非内存存储
从而降低了吞吐量,可尽量使用ssd硬盘来缓解吞吐量问题
144.rabbitmq 有几种广播类型?
三种广播模式:fanout,direct,topic
145.rabbitmq 怎么实现延迟消息队列?
通过消息过期后进入死信交换器再由交换器转发到延迟消费队列,实现延迟功能
使用RabbitMQ - deleayed - message-exchange 插件实现
146.rabbitmq 集群有什么用?
主要作用:
高可用:某个服务器出现问题,整个RabbitMQ还可以继续使用
高容量:集群可以承载更多的消息量
147.rabbitmq 节点的类型有哪些?
磁盘节点:消息会存储到磁盘
内存结点:消息都存储在内存中,重启服务器消息丢失,性能高于磁盘类型
148.rabbitmq 集群搭建需要注意哪些问题?
各节点之间使用“--link”连接,不能忽略
各节点使用的erlang cokkie值必须相同,此值相当于“密钥”的功能,用于各节点的认证
整个集群中必须包含一个磁盘节点
149.rabbitmq 每个节点是其他节点的完整拷贝吗?为什么?
不是
存储空间的考虑:如果每个节点都拥有所有队列的完全拷贝,这样的新增节点不当没有新增存储空间,反而增加了更
多的冗余数据
性能的考虑:如果每条消息都需要完整拷贝到每一个节点,那新增节点并没有提升处理消息的能力,最多时保持和单
节点相同的性能甚至是更糟
150.rabbitmq 集群中唯一一个磁盘节点崩溃了会发生什么情况?
不能创建队列
不能创建交换器
不能创建绑定
不能添加用户
不能更改权限
不能添加和删除集群节点
151.rabbitmq 对集群节点停止顺序有要求吗?
RabbitMQ 对集群的停止的顺序是有要求的,应该先关闭内存节点,最后再关闭磁盘节点。如果顺序恰好相反的话,
可能会造成消息的丢失。
十六、Zookeeper
157.zookeeper 是什么?
是一个分布式的,开发源码的分布式应用程序协调服务,一个分布式应用提供一致服务的软件,提供的功能:配置服
务,域名服务,分布式同步等
158.zookeeper 都有哪些功能?
集群管理,主节点选举,分布式锁,命名服务
159.zookeeper 有几种部署模式?
单机部署,集群部署,伪集群部署
160.zookeeper 怎么保证主从节点的状态同步?
zookeeper 的核心是原子广播,这个机制保证了各个 server 之间的同步。实现这个机制的协议叫做 zab 协议。 zab
协议有两种模式,分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,zab 就进入了恢
复模式,当领导者被选举出来,且大多数 server 完成了和 leader 的状态同步以后,恢复模式就结束了。状态同步保
证了 leader 和 server 具有相同的系统状态
161.集群中为什么要有主节点?
在分布式环境中,有些业务逻辑只需要集群中的某一台机器进行执行,其他的机器可以共享这个结果,这样可以大大减少重复计算,提高性能,所以就需要主节点
162.集群中有 3 台服务器,其中一个节点宕机,这个时候 zookeeper 还可以使用吗?
可以继续使用,单数服务器只要没超过一半的服务器宕机就可以继续使用。
163.说一下 zookeeper 的通知机制?
客户端端会对某个 znode 建立一个 watcher 事件,当该 znode 发生变化时,这些客户端会收到 zookeeper 的通
知,然后客户端可以根据 znode 变化来做出业务上的改变。
十七、MySql
164.数据库的三范式是什么?
一范式(1NF):强调的是列的原子性,即列不能够再分成其他几列。 第二范式(2NF):首先是 1NF,另外包含两
部分内容,一是表必须有一个主键;二是没有包含在主键中的列必须完全依 赖于主键,而不能只依赖于主键的一部
分。 第三范式(3NF):首先是 2NF,另外非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主
键列 A 依赖 于非主键列 B,非主键列 B 依赖于主键的情况。注:关系实质上是一张二维表,其中每一行是一个元
组,每一列是一个属性
165.一张自增表里面总共有 7 条数据,删除了最后 2 条数据,重启 mysql 数据库,又插入了
一条数据,此时 id 是几?
一般情况下,我们创建的表的类型是InnoDB,如果新增一条记录(不重启mysql的情况下),这条记录的id是8;但
是如果重启(文中提到的)MySQL的话,这条记录的ID是6。因为InnoDB表只把自增主键的最大ID记录到内存中,
所以重启数据库或者对表OPTIMIZE操作,都会使最大ID丢失。 但是,如果我们使用表的类型是MylSAM,那么这条
记录的ID就是8。因为MylSAM表会把自增主键的最大ID记录到数据文件里面,重启MYSQL后,自增主键的最大ID也
不会丢失。
166.如何获取当前数据库版本?
一种方法:
打开mysql在命令提示符上输入 select version();如图
第二种方法:在cmd里面输入 mysql -V 来获取mysql版本号
167.说一下 ACID 是什么?
1.Atomicity 原子性
2.Consistency 一致性
3.Isolation 隔离性
4.Durability 耐久性
168.char 和 varchar 的区别是什么?
char类型的长度是固定的,varchar的长度是可变的。
2.char类型的效率比varchar的效率稍高
3.varchar 与 varchar2的区别
varchar2是oracle开发的一个数据类型。
169.flfloat 和 double 的区别是什么?flfloat:占4个字节
double: 占8个字节
double 和 flfloat 的区别是double精度高,有效数字16位,flfloat精度7位(可提供7位或8位有效数字,构成包括符号
位、指数位和尾数位)。
但double消耗内存是flfloat的两倍,double的运算速度比flfloat慢得多,能用单精度时不要用双精度。
170.mysql 的内连接、左连接、右连接有什么区别?
1.内连接只显示两表中有关联的数据
2.左连接显示左表所有数据,右表没有对应的数据用NULL补齐,多了的数据删除
171.mysql 索引是怎么实现的?
B+树索引
一、按表列属性分类:
1.单列索引
以表的单个列字段创建的索引
2.联合索引
以表的多个列字段组合创建的索引,在查询条件使用索引的从左字段顺序才会生效,遵循最左匹配原则。
单列索引和联合索引又包括:
普通索引
非主键,非唯一列的索引
主键索引
基于该表主键自动生成成的索引,如果未给表定义主键,会查找该表中是否存在非空、整形、唯一索引作为其
主键(可通过select _rowid from 表名查看),若都不满足会隐式生成一个rowid作为主键(无法直接查到)
唯一索引
基于表的唯一列生成的索引,允许为空值
全文索引
将存储于数据库中的整本书或整篇文章中任意内容信息查找出来,如大量级的文字中如like %关键字%,普通索
引的效率与全文索引相比是非常低的。
172.怎么验证 mysql 的索引是否满足需求?
使用 explain 查看 SQL 是如何执行查询语句的,从而分析你的索引是否满足需求。 explain 语法:explain select *
from table where type=1。
173.说一下数据库的事务隔离?
Read uncommitted (读未提交):最低级别,以上问题均无法解决。
Read committed (读已提交):读已提交,可避免脏读情况发生。
Repeatable Read(可重复读):确保事务可以多次从一个字段中读取相同的值,在此事务持续期间,禁止其
他事务对此字段的更新,可以避免脏读和不可重复读,仍会出现幻读问题。
Serializable (串行化):最严格的事务隔离级别,要求所有事务被串行执行,不能并发执行,可避免脏读、不
可重复读、幻读情况的发生。
174.说一下 mysql 常用的引擎?
\1. InnoDB
InnoDB 的存储文件有两个,后缀名分别是 .frm 和 .idb,其中 .frm 是表的定义文件,而 idb 是数据文件。
InnoDB 中存在表锁和行锁,不过行锁是在命中索引的情况下才会起作用。InnoDB 支持事务,且支持四种隔离级别(读未提交、读已提交、可重复读、串行化),默认的为可重复读;而
在 Oracle 数据库中,只支持串行化级别和读已提交这两种级别,其中默认的为读已提交级别。
\2. Myisam
Myisam 的存储文件有三个,后缀名分别是 .frm、.MYD、MYI,其中 .frm 是表的定义文件,.MYD 是数据文
件,.MYI 是索引文件。
Myisam 只支持表锁,且不支持事务。Myisam 由于有单独的索引文件,在读取数据方面的性能很高 。
\3. 存储结构
InnoDB 和 Myisam 都是用 B+Tree 来存储数据的。
175.说一下 mysql 的行锁和表锁?
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
176.说一下乐观锁和悲观锁?
悲观锁 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别
人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其
它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之
前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断
一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这
样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中
java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
两种锁的使用场景 从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用
于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐
量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性
能,所以一般多写的场景下用悲观锁就比较合适。
乐观锁常见的两种实现方式 乐观锁一般会使用版本号机制或CAS算法实现。
177.mysql 问题排查都有哪些手段?
使用 show processlist 命令查看当前所有连接信息。
使用 explain 命令查询 SQL 语句执行计划。
开启慢查询日志,查看慢查询的 SQL。
178.如何做 mysql 的性能优化?
为搜索字段创建索引。
避免使用 select *,列出需要查询的字段。
垂直分割分表。
选择正确的存储引擎。
十八、Redis
179.redis 是什么?都有哪些使用场景?是一个高性能的(key/value)分布式内存数据库,基于内存运行 并支持持久化的NoSQL数据库,是当前最热门的
NoSql数据库之一, 也被人们称为数据结构服务器。
优点:
Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。 Redis不仅仅支
持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储 Redis支持数据的备份,即
master-slave模式的数据备份 应用场景:
内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务 取最新N个数据的操作,如:
可以将最新的10条评论的ID放在Redis的List集合里面 模拟类似于HttpSession这种需要设定过期时间的功能 发布、
订阅消息系统 定时器、计数器
180.redis 有哪些功能?
基于本机内存的缓存,服务端的Redis,持久化(Persistence)哨兵(Sentinel)和复制,(Replication)集群
(Cluster)
181.redis 和 memecache 有什么区别?
区别:
1、存储方式不同
memecache 把数据全部存在内存之中,断电后会挂掉,数据不能超过内存大小;redis有部份存在硬盘上,这
样能保证数据的持久性,支持数据的持久化(笔者注:有快照和AOF日志两种持久化方式,在实际应用的时
候,要特别注意配置文件快照参数,要不就很有可能服务器频繁满载做dump)。
2、数据支持类型不同
redis在数据支持上要比memecache多的多。
3、使用底层模型不同
新版本的redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请
求。
4、运行环境不同
redis目前官方只支持LINUX 上去e5a48de588b6e799bee5baa6e79fa5e9819331333365666237行,从而省去
了对于其它系统的支持,这样的话可以更好的把精力用于本系统 环境上的优化,虽然后来微软有一个小组为其
写了补丁。但是没有放到主干上。
182.redis 为什么是单线程的?
redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为多线程的本质就是
CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切换,对于一个内存的系统来
说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存的数据,然后针对这块内存的数据进行多
次读写的时候,都是在一个CPU上完成的,所以它是单线程处理这个事。在内存的情况下,这个方案就是最佳方案
183.什么是缓存穿透?怎么解决?
一.缓存穿透:
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这
个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决办法:
1.布隆过滤对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃。还有最常见的则是采用布隆过滤
器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避
免了对底层存储系统的查询压力。
\2. 缓存空对象. 将 null 变成一个值.
也可以采用一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们
仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
二.缓存雪崩
解决方法
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写
缓存,其他线程等待。
业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去
load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set
一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过
期时间,让缓存失效的时间点尽量均匀
3.做二级缓存,或者双缓存策略。
4.缓存永远不过期
这里的“永远不过期”包含两层意思:
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是
老数据,但是对于一般的互联网功能来说这个还是可以忍受。
184.redis 支持的数据类型有哪些?
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式
保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
1. 加锁排队. 限流-- 限流算法. 1.计数 2.滑动窗口 3. 令牌桶Token Bucket 4.漏桶 leaky bucket [1]
2.数据预热
A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。
(1) 从缓存上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
(2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,
通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期.1、string(字符串)
与memcached一样,一个key对应一个value,key的最大存储值为512MB,value的最大存储值也为512MB。
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象。使用
设置和获取的命令为SET和GET。
命令为【SET key value】【GET key】
2、hash(哈希)
键值(key=>value)对集合。 是一个 string 类型的 fifield 和 value 的映射表,hash 特别适合用于存储对象(每个
hash可以存储2的32次方 -1 键值对(40多亿))。使用设置和获取的命令为 HMSET, HGET。
命令为【HMSET key key1 value1 key2 value2】【HGET key key1】
3、list(列表)
列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部或者尾部(列表最多可存储2
的32次方 - 1 元素 (4294967295, 每个列表可存储40多亿))。进值命令为LPUSH或者RPUSH,获取值命令为
LRANGE。
命令为【LPUSH key value】【LRANGE key 0 10】获取key列表从左边开始0到10个value。
4、set(集合)
Set 是 string 类型的无序集合。集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。集合中
最大的成员数为 2的32次方 - 1(4294967295, 每个集合可存储40多亿个成员)。SADD添加一个 string 元素到
key 对应的 set 集合中,成功返回 1,如果元素已经在集合中返回 0。
命令为【SADD key value】【SMEMBERS key】
5、zset(有序集合)
和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分
数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重
复。ZADD添加元素到集合,元素在集合中存在则更新对应score。
185.redis 支持的 java 客户端都有哪些?
Redisson,Jedis,lettuce等等,官方推荐使用Redisson。
186.jedis 和 redisson 有哪些区别?
Jedis 和 Redisson 都是Java中对Redis操作的封装。Jedis 只是简单的封装了 Redis 的API库,可以看作是Redis客户
端,它的方法和Redis 的命令很类似。Redisson 不仅封装了 redis ,还封装了对更多数据结构的支持,以及锁等功
能,相比于Jedis 更加大。但Jedis相比于Redisson 更原生一些,更灵活
187.怎么保证缓存和数据库数据的一致性?
将不一致分为三种情况:
\1. 数据库有数据,缓存没有数据;
\2. 数据库有数据,缓存也有数据,数据不相等;
\3. 数据库没有数据,缓存有数据。
保证缓存和数据库数据的一致性:
\1. 首先尝试从缓存读取,读到数据则直接返回;如果读不到,就读数据库,并将数据会写到缓存,并返回。
\2. 需要更新数据时,先更新数据库,然后把缓存里对应的数据失效掉(删掉)。
188.redis 持久化有几种方式?
RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时
候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
189.redis 怎么实现分布式锁?
分布式锁常见的三种实现方式:
1. 数据库乐观锁;
2. 基于Redis的分布式锁;
3. 基于ZooKeeper的分布式锁。
Redis的分布式锁;
1.加锁
最简单的方法是使用setnx命令。key是锁的唯一标识,按业务来决定命名。比如想要给一种商品的秒杀活动加
锁,可以给key命名为 “lock_sale_商品ID” 。而value设置成什么呢?锁的value值为一个随机生成的UUID。我们
可以姑且设置成1。加锁的伪代码如下:
setnx(key,1) 当一个线程执行setnx返回1,说明key原本不存在,该线程成功得到了锁;当一个线程执行
setnx返回0,说明key已经存在,该线程抢锁失败。
2.解锁
有加锁就得有解锁。当得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入。释放锁的最简单方式
是执行del指令,伪代码如下:
del(key) 释放锁之后,其他线程就可以继续执行setnx命令来获得锁。
3.锁超时
锁超时是什么意思呢?如果一个得到锁的线程在执行任务的过程中挂掉,来不及显式地释放锁,这块资源将会
永远被锁住,别的线程再也别想进来。
所以,setnx的key必须设置一个超时时间,单位为second,以保证即使没有被显式释放,这把锁也要在一定时
间后自动释放,避免死锁。setnx不支持超时参数,所以需要额外的指令,伪代码如下:
expire(key, 30)
190.redis 分布式锁有什么缺陷?
1. Redis 分布式锁不能解决超时的问题,
2. 分布式锁有一个超时时间,程序的执行如果超出了锁的超时时间就会出现问题。
191.redis 如何做内存优化?
192.redis 淘汰策略有哪些?193.redis 常见的性能问题有哪些?该如何解决?
十九、JVM
194.说一下 jvm 的主要组成部分?及其作用?
1.类加载器(Class Loader):加载类文件到内存。Class loader只管加载,只要符合文件结构就加载,至于能否运
行,它不负责,那是有Exectution Engine 负责的。
2.执行引擎(Execution Engine):也叫解释器,负责解释命令,交由操作系统执行。
3.本地库接口(Native Interface):本地接口的作用是融合不同的语言为java所用
4.运行时数据区(Runtime Data Area):** **
(1)堆。堆是java对象的存储区域,任何用new字段分配的java对象实例和数组,都被分配在堆上,java堆可用-
Xms和-Xmx进行内存控制,jdk1.7以后,运行时常量池从方法区移到了堆上。新生代:老年代 = 1:2
Eden:s1: s2=8:1:1
(2)方法区:用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。
误区:方法区不等于永生代
很多人原因把方法区称作“永久代”(Permanent Generation),本质上两者并不等价,只是HotSpot虚拟机垃
圾回收器团队把GC分代收集扩展到了方法区,或者说是用来永久代来实现方法区而已,这样能省去专门为方法区编
写内存管理的代码,但是在Jdk8也移除了“永久代”,使用Native Memory来实现方法区。
(3)虚拟机栈:虚拟机栈中执行每个方法的时候,都会创建一个栈桢用于存储局部变量表,操作数栈,动态链接,
方法出口等信息。
(4)本地方法栈:与虚拟机发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native
方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信
息。
(5)程序计数器。指示Java虚拟机下一条需要执行的字节码指令。
195.说一下 jvm 运行时数据区?
题外话,被网上各个版本的观点搞蒙了,整理一下。
Java内存分JVM内存和本地内存(Native Memory)两块,这个不能搞混。JVM内存由JVM管理,是虚拟的内存地址。
本地内存直接使用进程内存,当进程无法申请内存时,同样也会出现内存溢出和栈深度耗尽的异常。
JVM内存,从逻辑上划分为JVM栈区、JVM堆区、程序计数器。方法区、本地方法栈和本地堆都位于本地内存,但有
些虚拟机(HotSpot)的本地方法栈和虚拟机栈是合并在一起的。
JVM运行时数据区包括JVM栈区 、本地方法栈区、堆区、方法区、程序计数器。(网上通用的说法)
1. 程序计数器
程序计数器空间很小,各线程私有,只用于存放线程当前运行的行号,不存在内存溢出的问题。
2. 方法区
方法区用于存放类和方法的元数据、静态变量、常量、即时编译后的代码。简单来讲,就是不常变化的东西。但要注意的是java对象本身永远都不会置入静态存储空间。解释一下:元数据是类的代码信息,保存在方法区;数值
类型数据直接保存在代码内部;引用类型变量,引用放在代码内部,但是实例放在堆上。所以,自动装箱的整型、字
符串数据在方法区只会保存一个实例常量池上的引用。
面试中常问道的永久代(Permanent Generation space)是HotSpot类型的JVM虚拟机对方法区的物理实现,其他虚
拟机有其他的实现方式。jsp运行过程中容易出现内存溢出,就是因为即时编译的代码过多,导致永久代满了。
JDK1.7后取消了永久代,方法区移动到了本地内存(Native Memory)中,称为MetaSpace,与JVM内存无关了(受
进程内存分配影响,还是有可能会内存溢出的)。
常量池位于方法区。JDK1.6之前,常量池的具体实例也在方法区,很难找到标准答案,但从String.intern()方法的变
动来看,这句话应该是对的。JDK1.7之后,常量池在本地内存中只保存引用符号,常量池的所有对象保存在堆上。在
讲常量池的时候最好还是用符号常量池和实例常量池区别一下。
插一段整型常量池的实现。整型的包装类Integer,自动装箱过程调用的是valueOf方法(感谢这位兄台深入剖析Java
中的装箱和拆箱 - 海 子 - 博客园的文章),整型常量池使用IntegerCache类+整型数组实现的。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//IntegerCache的关键代码(在静态代码块中)
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
...
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
从源码中可以看到,常量池上的整型数据是在IntegerCache类加载时就生成完成的,具体生成的范围是由JVM环境决
定的。也就是,常量并不是动态添加到常量池的,呵~果然骚操作~Double类就没有这样的操作,这就解释了,为什
么整型计算后的结果在常量池上,而浮点型计算结果为什么不在常量池,人家“出生前”就已经送到了常量池啊。
String常量池的实现是看不到的,且valueOf方法返回的是new出来的新对象。intern方法是native方法,无法得知其
实现。
3.栈区
栈区,是个临时的数据存放区。与C语言不同,栈区只在方法调用时才会被用到。栈区首先存放的是栈帧,每调用一
个方法,便会产生一个栈帧,并入栈。栈帧存放存放局部变量表、操作数栈、动态链接、返回地址。栈帧内部也有一
个栈的结构,根据元数据信息分配栈的深度。根据指令执行顺序,将操作数压栈保存、出栈运算,移动到局部变量
表。方法执行完,出栈,根据返回地址继续执行调用它的方法的栈帧。
4.堆区
堆区就是用来保存引用类型的实例。堆上的实例会存储它所属类的类信息,用于找到该类及该类的方法,在获取一个
对象的类名信息时,即时该对象被向上转型了,还是能够取得该实例化该对象的类的信息。通过类信息,准确的调用
类中的成员方法。196.说一下堆栈的区别?
堆栈与堆区别为:空间不同、地址方向来不同、释放不同。
一、空间不同
1、堆栈:堆栈是自动分配变量,以及函数调用的时候所使用的一些空间。
2、堆:堆是是由malloc之类函数分配的空间所在地。
二、地址方向不同
1、堆栈:堆栈的地址方向是由高向低减少性扩展,有总长度自大小限制。
2、堆:堆的地址方向是由低向高增长性扩展,没有总长度大小限制。
三、释放不同
1、堆栈:堆栈由编译器自动释放,存放函数的参数值,局部变量的zhidao值等。
2、堆:堆由程序员人工进行释放, 若程序员不释放,程序结束时可能由OS回收 。
197.队列和栈是什么?有什么区别?
1.队列(Queue):是限定只能在表的一端进行插入和在另一端进行删除操作的线性表
2.栈(Stack):是限定只能在表的一端进行插入和删除操作的线性表
3.队列先进先出(FIFO),栈先进后出(FILO)
198.什么是双亲委派模型?
类加载器有哪些:1、启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在
Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(例如rt.jar)类库加载到虚拟机内存中;
2、扩展类加载器(Extension ClassLoader):它负责加载
统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器;
3、应用程序类加载器(Application ClassLoader):由于这个类加载器是ClassLoader中的
getSystemClassLoader()方法的返回值,所以一般也称为系统类加载器。它负责加载用户类路径(claspath)上所指
定的类库,开发者可以直接使用这个类加载器。
4、线程上下文类加载器,面试的时候知道的也可以说下,它是为了解决基础类掉调用回用户代码而引入的设计,也
就是兼容双亲委派模型的缺陷。
类加载器双亲委派模型如下:
破坏双亲委派模型的三大情况:
1、双亲委派模型的第一次“被破坏”是发生在双亲委派模型出现之前——即JDK1.2发布之前;2、双亲委派模型的第二次“被破坏”是由于模型自身的缺陷导致的;
一个典型的例子便是JNDI服务,JNDI现在已经是Java的标准服务,它的代码由启动类加载器去加载(在JDK1.3时
放进去的rt.jar),但JNDI的目的就是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署在应用程序的
Classpath下的JNDI接口提供者(SPI)的代码,但启动类记载不可能“认识”这些代码?
为了解决这个问题,Java设计团队只好引入了一个不太优雅的设计,线程上下文类加载器。这个类加载器可以通
过java.lang.Thread类的setContextClassLoader方法设置,如果创建线程时还未设置,它将从父线程中继承一个,
如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
有了这个线程上下文来加载器,就可以做一些“舞弊”的事情了,JNDI服务使用这个县城上下文类加载器去加载所
需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载器的动作,这种行为实际上打通了双亲委派模型的
层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则;
java中所有涉及SPI的加载懂基本上都采用这种方式,例如JNDI、JDBC、JCE、JAXB和JBI等。
3、双亲委派模型的第三次“被破坏”是由于用户对程序动态性的追求而导致的,即热部署(OSGI实现模块化热部署)
199.说一下类加载的执行过程?
java程序在执行过程中,类,对象以及它们成员加载、初始化的顺序如下: 1、首先加载要创建对象的类及其直接复
与间接父类。 2、在类被加载的同时会将静态成员进行加载,主要包括静态成员变量的初始化,静态语制句块的执
行,在加载时按代码的先后顺序进行。 3、需要的类加载完成后,开始创建对象,首先会加载非静态的成员,主要包
括非静态成员变量的初始化,非静态语句块的执行,在加载时按代码的先后顺序进行。 4、最后执zd行构造器,构造
器执行完毕,对象生成。
200.怎么判断对象是否可以被回收?
这就是所谓的对象存活性判断,常用的方法有两种:1.引用计数法; 2.对象可达性分析。由于引用计数法存在互相引
用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析算法。
1、引用计数法
引用计数法的逻辑非常简单,但是存在问题,java并不采用这种方式进行对象存活判断。
引用计数法的逻辑是:在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之
相连,则将counter++。如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废
弃,不处于存活状态。
这种方法来标记对象的状态会存在很多问题:
jdk从1.2开始增加了多种引用方式:软引用、弱引用、虚引用,且在不同引用情况下程序应进行不同的操作。如果我们只采用
一个引用计数法来计数无法准确的区分这么多种引用的情况。
1
2、可达性分析算法在主流的商用程序语言中(Java和C#),都是使用可达性分析算法判断对象是否存活的。这个算法的基本思路就是通过
一系列名为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference
Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6,
object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。
201.java 中都有哪些引用类型?
1、Java中有哪几种引用?它们的含义和区别是什么?
从JDK1.2开始,Java中的引用类型分为四种,分别是: ①强引用(StrongReference) ②软引用(SoftRefernce)
③弱引用(WeakReference) ④虚引用(PhantomReference)
强引用-StrongReference
这种引用是平时开发中最常用的,例如 String strong = new String("Strong Reference") ,当一个实例对象
具有强引用时,垃圾回收器不会回收该对象,当内存不足时,宁愿抛出OutOfMemeryError异常也不会通过回收强引
用的对象,因为JVM认为强引用的对象是用户正在使用的对象,它无法分辨出到底该回收哪个,强行回收有可能导致
系统严重错误。
软引用-SoftReference
如果一个对象只有软引用,那么只有当内存不足时,JVM才会去回收该对象,其他情况不会回收。软引用可以结合
ReferenceQueue来使用,当由于系统内存不足,导致软引用的对象被回收了,JVM会把这个软引用加入到与之相关
联的ReferenceQueue中。
ReferenceQueue referenceQueue = new ReferenceQueue();
SoftReference
Book book = softReference.get();
Reference reference = referenceQueue.poll();
当系统内存不足时,触发gc,这个Book就会被回收,reference 将不为null。
弱引用-WeakReference
只有弱引用的对象,当JVM触发gc时,就会回收该对象。与软引用不同的是,不管是否内存不足,弱引用都会被回
收。弱引用可以结合ReferenceQueue来使用,当由于系统触发gc,导致软引用的对象被回收了,JVM会把这个弱引
用加入到与之相关联的ReferenceQueue中,不过由于垃圾收集器线程的优先级很低,所以弱引用不一定会被很快回
收。下面通过一个主动触发gc的例子来验证此结论。
ReferenceQueue referenceQueue = new ReferenceQueue();
WeakReference
Book book = softReference.get();
System.gc();
//Runtime.getRuntime().gc();
Reference reference = referenceQueue.poll();weak_reference.png
当然这不是每次都能复现,因为我们调用System.gc()只是告诉JVM该回收垃圾了,但是它什么时候做还是不一定的,
但就我测试来看,只要多写几次System.gc(),复现的概率还是很高的。
虚引用-PhantomReference
如果一个对象只有虚引用在引用它,垃圾回收器是可以在任意时候对其进行回收的,虚引用主要用来跟踪对象被垃圾
回收器回收的活动,当被回收时,JVM会把这个弱引用加入到与之相关联的ReferenceQueue中。与软引用和弱引用
不同的是,虚引用必须有一个与之关联的ReferenceQueue,通过phantomReference.get()得到的值为null,试想一
下,如果没有ReferenceQueue与之关联还有什么存在的价值呢?
202.说一下 jvm 有哪些垃圾回收算法?
垃圾回收算法有很多种,目前商业虚拟机常用的是分代回收算法,但最初并不是用这个算法的 我们来看一下垃圾收
集算法的背景知识
标记-清除算法 最基础的垃圾回收算法,顾名思义,整个回收过程分两步: 1.逐个标记 2.统一回收 该算法可以算是后
来所有垃圾回收算法的基石(后续所有算法都有标记和清除这两步,只不过策略上有了一些优化) 这里值得一说的
是这个标记 虚拟机是如何判断一个对象是“活”还是“死”? 因此又引出两种标记算法: 1.引用计数算法 引用计数算法
非常简单且高效,当一个对象被引用一次则+1不再被引用则-1,当计数为0就是不可能在被使用的对象了,但是这种
算法存在一个致命的缺陷:两个对象相互引用对方呢?所以,这种算法肯定不能用,pass掉 2.可达性分析算法 目前
的标记算法主流实现都是用的可达性分析算法。就是以一个叫GC Roots的对象为起点,通过引用链向下搜索,如果
一个对象通过引用链无法与GC Roots对象链接,就视为可回收对象,上面说的那种相互引用的情况自然也解决了。
扩展:即使是可达性分析中不可达的对象也并不是非死不可,只是暂处‘缓刑’,真正宣告一个对象死亡至少还要经历
两次标记过程:当被判定不可达之后那么他被第一次标记并进行筛选,若对象没有覆盖fifinalize()方法或者fifinalize()方
法已经被虚拟机调用过就‘放生’,如果被判定需要执行fifinalize()方法就会被放到一个叫F-Queue的队列中进行第二次
标记对象被再次被引用就会放生,否则就会被回收。 fifinalize()方法 fifinalize()是Object中的方法,当垃圾回收器将要
回收对象所占内存之前被调用,即当一个对象被虚拟机宣告死亡时会先调用它fifinalize()方法,让此对象处理它生前的
最后事情(这个对象可以趁这个时机挣脱死亡的命运)
说到这里敏锐的小伙伴可能以及察觉到了,上面都在说引用所以引用的定义就显得尤为关键了 JDK1.2后Java对引用
的概念进行了扩充,将引用分为:强引用、软引用、弱引用、虚引用四种强引用:用处很大,无论如何都不会被GC回收
软引用:有一定用处但不大,内存实在不够才会在内存溢出之前回收掉
弱引用:比软引用强度更弱一些,GC会让它多活一轮,下一轮就回收
虚引用:必回收,唯一作用就是被GC回收时会收到一个系统通知
复制算法 前面说的标记-清除算法其实两个过程效率都很低,并且回收之后内存被‘抠出很多洞’内存碎片化严重,此时
如果过来了一个较大的对象,找不到一整块连续的内存空间就不得不提前触发另外一次GC回收。 而复制算法则选择
将内存一分为二每次只使用其中一半,满了之后将存活的对象整齐复制到另一块干净的内存上,将剩下的碎片一次性
擦除,简单高效。但是也存在一个很大的缺陷,那就是可用内存变为原来的一半了。
分代收集算法 事实上后来IBM公司经过研究发现,98%的对象都是‘朝生夕死’,所以并不需要1:1的划分内存,即我们
现在常用的分代收集算法: 根据对象的存活周期将内存划分为两块,分别为新生代和老年代,然后对各代采用不同
的回收算法,在新生代中大部分是‘朝生夕死’的对象,继续将新生代8:2划分为Eden区和survival区,其中survival区
1:1分成s0和s1两块,采用之前说的复制算法,减少内存碎片的产生。 新生代满了会进行一次minor GC ,minor GC
存活的对象转移到survival区,survival区满了就会将survival区进行回收,存活的survival区对象复制到另外一块
survival区中,并且survival区对象每存活一轮年龄+1当到达一定年龄就会前往老年代。
扩展01:JVM何时会进行全局GC
01.手动调用System.GC 但也不是立即调用
02.老年代空间不足
03.永生代空间不足
04.计算得知新生代前往老年代平均值大于老年代剩余空间
扩展02:在压力测试时,发现FullGC频率很高,如何解决
01.观察GC日志,判断是否有内存泄漏,或者存在内部不合理点
02.调整JVM参数,如新生代、老年代大小 S0+S1大小比例,选用不同的立即回收器
03.Dump内存,做进一步的对象分析
04.压测脚本的编写,性能问题解决前可以发现问题,并对解决方案进行验证
203.说一下 jvm 有哪些垃圾回收器?
新生代收集器:
Serial ParNew Parallel Scavenge
老年代收集器:
Serial Old CMS Parallel Old
堆内存垃圾收集器:
G1
204.详细介绍一下 CMS 垃圾回收器?
垃圾回收器从线程运行情况分类有三种串行回收,Serial回收器,单线程回收,全程stw; 并行回收,名称以Parallel开头的回收器,多线程回收,全程
stw; 并发回收,cms与G1,多线程分阶段回收,只有某阶段会stw; CMS垃圾回收特点 cms只会回收老年代和永久
带(1.8开始为元数据区,需要设置CMSClassUnloadingEnabled),不会收集年轻带; cms是一种预处理垃圾回收
器,它不能等到old内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败;所以cms垃圾
回收器开始执行回收操作,有一个触发阈值,默认是老年代或永久带达到92%; 3. CMS cms是最常用的垃圾垃圾回
收器,下面分析下CMS垃圾回收器工作原理;
CMS 处理过程有七个步骤:
1. 初始标记(CMS-initial-mark) ,会导致swt;
2. 并发标记(CMS-concurrent-mark),与用户线程同时运行;
3. 预清理(CMS-concurrent-preclean),与用户线程同时运行;
4. 可被终止的预清理(CMS-concurrent-abortable-preclean) 与用户线程同时运行;
5. 重新标记(CMS-remark) ,会导致swt;
6. 并发清除(CMS-concurrent-sweep),与用户线程同时运行;
7. 并发重置状态等待下次CMS的触发(CMS-concurrent-reset),与用户线程同时运行;
205.新生代垃圾回收器和老生代垃圾回收器都有哪些?有什么区别?
新生代回收器:Serial、ParNew、Parallel Scavenge
老年代回收器:Serial Old、Parallel Old、CMS
整堆回收器:G1
新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用
的是标记-整理的算法进行垃圾回收。
206.简述分代垃圾回收器是怎么工作的?
分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。 新生代
使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,
它的执行流程如下:
把 Eden + From Survivor 存活的对象放入 To Survivor 区;
清空 Eden 和 From Survivor 分区; From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To
Survivor 变 From Survivor。
每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级
为老生代。大对象也会直接进入老生代。
老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。
以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。
207.说一下 jvm 调优的工具?
*Jconsole : jdk自带,功能简单,但是可以在系统有一定负荷的情况下使用。对垃圾回收算法有很详细的跟踪。**JProfifiler:商业软件,需要付费。功能强大。*
*VisualVM:JDK自带,功能强大,与JProfifiler类似。推荐。*
208.常用的 jvm 调优的参数都有哪些?
本文章参数根据后期用的参数会持续更新 ---
(1)-Xms20M
表示设置JVM启动内存的最小值为20M,必须以M为单位
(2)-Xmx20M
表示设置JVM启动内存的最大值为20M,必须以M为单位。将-Xmx和-Xms设置为一样可以避免JVM内存自动扩展。大
的项目-Xmx和-Xms一般都要设置到10G、20G甚至还要高
(3)-verbose:gc
表示输出虚拟机中GC的详细情况
(4)-Xss128k
表示可以设置虚拟机栈的大小为128k
(5)-Xoss128k
表示设置本地方法栈的大小为128k。不过HotSpot并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说这个参数
是无效的
(6)-XX:PermSize=10M
表示JVM初始分配的永久代(方法区)的容量,必须以M为单位
(7)-XX:MaxPermSize=10M
表示JVM允许分配的永久代(方法区)的最大容量,必须以M为单位,大部分情况下这个参数默认为64M
(8)-Xnoclassgc
表示关闭JVM对类的垃圾回收
(9)-XX:+TraceClassLoading
表示查看类的加载信息
(10)-XX:+TraceClassUnLoading
表示查看类的卸载信息
(11)-XX:NewRatio=4
表示设置 年轻代(包括Eden和两个Survivor区)/老年代 的大小比值为1:4,这意味着年轻代占整个堆的1/5
(12)-XX:SurvivorRatio=8表示设置2个Survivor区:1个Eden区的大小比值为2:8,这意味着Survivor区占整个年轻代的1/5,这个参数默认为8
(13)-Xmn20M
表示设置年轻代的大小为20M
(14)-XX:+HeapDumpOnOutOfMemoryError
表示可以让虚拟机在出现内存溢出异常时Dump出当前的堆内存转储快照
(15)-XX:+UseG1GC
表示让JVM使用G1垃圾收集器
(16)-XX:+PrintGCDetails
表示在控制台上打印出GC具体细节
(17)-XX:+PrintGC
表示在控制台上打印出GC信息
(18)-XX:PretenureSizeThreshold=3145728
表示对象大于3145728(3M)时直接进入老年代分配,这里只能以字节作为单位
(19)-XX:MaxTenuringThreshold=1
表示对象年龄大于1,自动进入老年代,如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于
年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,
这样可以增加对象在年轻代的存活时间,增加在年轻代被回收的概率。
(20)-XX:CompileThreshold=1000
表示一个方法被调用1000次之后,会被认为是热点代码,并触发即时编译
(21)-XX:+PrintHeapAtGC
表示可以看到每次GC前后堆内存布局
(22)-XX:+PrintTLAB
表示可以看到TLAB的使用情况
(23)-XX:+UseSpining
开启自旋锁
(24)-XX:PreBlockSpin
更改自旋锁的自旋次数,使用这个参数必须先开启自旋锁
(25)-XX:+UseSerialGC
表示使用jvm的串行垃圾回收机制,该机制适用于丹cpu的环境下
(26)-XX:+UseParallelGC
表示使用jvm的并行垃圾回收机制,该机制适合用于多cpu机制,同时对响应时间无强硬要求的环境下,使用-
XX:ParallelGCThreads=设置并行垃圾回收的线程数,此值可以设置与机器处理器数量相等。
(27)-XX:+UseParallelOldGC表示年老代使用并行的垃圾回收机制
(28)-XX:+UseConcMarkSweepGC
表示使用并发模式的垃圾回收机制,该模式适用于对响应时间要求高,具有多cpu的环境下
(29)-XX:MaxGCPauseMillis=100
设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值。
(30)-XX:+UseAdaptiveSizePolicy
设置此选项后,并行收集器会自动选择年轻代区大小和相应的Survivor区比例,以达到目标系统规定的最低响应时间
或者收集频率等,此值建议使用并行收集器时,一直打开
算法题
1)请简单解释算法是什么?
算法是一个定义良好的计算过程,它将一些值作为输入并产生相应的输出值。简单来说,它是将输入转换为输出的一
系列计算步骤。
2)解释什么是快速排序算法?
快速排序算法能够快速排序列表或查询。它基于分割交换排序的原则,这种类型的算法占用空间较小,它将待排序列
表分为三个主要部分:
小于Pivot的元素
枢轴元素Pivot(选定的比较值)
大于Pivot的元素
3)解释算法的时间复杂度?
算法的时间复杂度表示程序运行完成所需的总时间,它通常用大O表示法来表示。
4)请问用于时间复杂度的符号类型是什么?
用于时间复杂度的符号类型包括:
Big Oh:它表示小于或等于目标多项式
Big Omega:它表示大于或等于目标多项式
Big Theta:它表示与目标多项式相等
Little Oh:它表示小于目标多项式
Little Omega:它表示大于目标多项式
5)解释二分法检索如何工作?
在二分法检索中,我们先确定数组的中间位置,然后将要查找的值与数组中间位置的值进行比较,若小于数组中间
值,则要查找的值应位于该中间值之前,依此类推,不断缩小查找范围,直至得到最终结果。
6)解释是否可以使用二分法检索链表?
由于随机访问在链表中是不可接受的,所以不可能到达O(1)时间的中间元素。因此,对于链表来说,二分法检索
是不可以的(对顺序链表或排序后的链表是可以用的)。
7)解释什么是堆排序?堆排序可以看成是选择排序的改进,它可以定义为基于比较的排序算法。它将其输入划分为未排序和排序的区域,通
过不断消除最小元素并将其移动到排序区域来收缩未排序区域。
8)说明什么是Skip list?
Skip list数据结构化的方法,它允许算法在符号表或字典中搜索、删除和插入元素。在Skip list中,每个元素由一个节
点表示。搜索函数返回与key相关的值的内容。插入操作将指定的键与新值相关联,删除操作可删除指定的键。
9)解释插入排序算法的空间复杂度是多少?
插入排序是一种就地排序算法,这意味着它不需要额外的或仅需要少量的存储空间。对于插入排序,它只需要将单个
列表元素存储在初始数据的外侧,从而使空间复杂度为O(1)。
10)解释什么是“哈希算法”,它们用于什么?
“哈希算法”是一个哈希函数,它使用任意长度的字符串,并将其减少为唯一的固定长度字符串。它用于密码有效性、
消息和数据完整性以及许多其他加密系统。
11)解释如何查找链表是否有循环?
要知道链表是否有循环,我们将采用两个指针的方法。如果保留两个指针,并且在处理两个节点之后增加一个指针,
并且在处理每个节点之后,遇到指针指向同一个节点的情况,这只有在链表有循环时才会发生。
12)解释加密算法的工作原理?
加密是将明文转换为称为“密文”的密码格式的过程。要转换文本,算法使用一系列被称为“键”的位来进行计算。密钥
越大,创建密文的潜在模式数越多。大多数加密算法使用长度约为64到128位的固定输入块,而有些则使用流方法。
13)列出一些常用的加密算法?
一些常用的加密算法是:
3-way
Blowfifish
CAST
CMEA
GOST
DES 和Triple DES
IDEA
LOKI等等
14)解释一个算法的最佳情况和最坏情况之间有什么区别?
·最佳情况:算法的最佳情况解释为算法执行最佳的数据排列。例如,我们进行二分法检索,如果目标值位于正在搜
索的数据中心,则这就是最佳情况,最佳情况时间复杂度为0。
·最差情况:给定算法的最差输入参考。例如快速排序,如果选择关键值的子列表的最大或最小元素,则会导致最差
情况出现,这将导致时间复杂度快速退化到O(n2)。
15)解释什么是基数排序算法?
基数排序又称“桶子法”,是通过比较数字将其分配到不同的“桶里”来排序元素的。它是线性排序算法之一。
16)解释什么是递归算法?
递归算法是一个解决复杂问题的方法,将问题分解成较小的子问题,直到分解的足够小,可以轻松解决问题为止。通
常,它涉及一个调用自身的函数。17)提到递归算法的三个定律是什么?
所有递归算法必须遵循三个规律
1. 递归算法必须有一个基点
2. 递归算法必须有一个趋向基点的状态变化过程
3. 递归算法必须自我调用
18)解释什么是冒泡排序算法?
冒泡排序算法也称为下沉排序。在这种类型的排序中,要排序的列表的相邻元素之间互相比较。如果它们按顺序排列
错误,将交换值并以正确的顺序排列,直到最终结果“浮”出水面。
Linux常用命令
https://blog.csdn.net/qq_23329167/article/details/83856430
一、基本命令
1.1 关机和重启
关机 shutdown -h now 立刻关机 shutdown -h 5 5分钟后关机 poweroffff 立刻关机 重启 shutdown -r now 立刻重启
shutdown -r 5 5分钟后重启 reboot 立刻重启
1.2 帮助命令
--help命令 shutdown --help: ifconfifig --help:查看网卡信息
man命令(命令说明书) man shutdown 注意:man shutdown打开命令说明书之后,使用按键q退出
二、目录操作命令
2.1 目录切换 cd
命令:cd 目录
cd / 切换到根目录 cd /usr 切换到根目录下的usr目录 cd ../ 切换到上一级目录 或者 cd .. cd ~ 切换到home目录 cd -
切换到上次访问的目录
2.2 目录查看 ls [-al]
命令:ls [-al]
ls 查看当前目录下的所有目录和文件 ls -a 查看当前目录下的所有目录和文件(包括隐藏的文件) ls -l 或 ll 列表查看
当前目录下的所有目录和文件(列表查看,显示更多信息) ls /dir 查看指定目录下的所有目录和文件 如:ls /usr
2.3 目录操作【增,删,改,查】 2.3.1 创建目录【增】 mkdir 命令:mkdir 目录
mkdir aaa 在当前目录下创建一个名为aaa的目录 mkdir /usr/aaa 在指定目录下创建一个名为aaa的目录
2.3.2 删除目录或文件【删】rm
命令:rm [-rf] 目录
删除文件: rm 文件 删除当前目录下的文件 rm -f 文件 删除当前目录的的文件(不询问)删除目录: rm -r aaa 递归删除当前目录下的aaa目录 rm -rf aaa 递归删除当前目录下的aaa目录(不询问)
全部删除: rm -rf * 将当前目录下的所有目录和文件全部删除 rm -rf /* 【自杀命令!慎用!慎用!慎用!】将根目
录下的所有文件全部删除
注意:rm不仅可以删除目录,也可以删除其他文件或压缩包,为了方便大家的记忆,无论删除任何目录或文件,都
直接使用 rm -rf 目录/文件/压缩包
2.3.3 目录修改【改】mv 和 cp
一、重命名目录
命令:mv 当前目录 新目录
例如:mv aaa bbb 将目录aaa改为bbb
注意:mv的语法不仅可以对目录进行重命名而
且也可以对各种文件,压缩包等进行 重命名的操作
二、剪切目录
命令:mv 目录名称 目录的新位置
示例:将/usr/tmp目录下的aaa目录剪切到 /usr目录下面 mv /usr/tmp/aaa /usr
注意:mv语法不仅可以对目录进行剪切操作,对文件和压缩包等都可执行剪切操作
三、拷贝目录
命令:cp -r 目录名称 目录拷贝的目标位置 -r代表递归
示例:将/usr/tmp目录下的aaa目录复制到 /usr目录下面 cp
/usr/tmp/aaa /usr
注意:cp命令不仅可以拷贝目录还可以拷贝文件,压缩包等,拷贝文件和压缩包时不 用写-r递归
2.3.4 搜索目录【查】fifind
命令:fifind 目录 参数 文件名称 示例:fifind /usr/tmp -name 'a*' 查找/usr/tmp目录下的所有以a开头的目录或文件
三、文件操作命令
3.1 文件操作【增,删,改,查】
3.1.1 新建文件【增】touch
命令:touch 文件名 示例:在当前目录创建一个名为aa.txt的文件 touch aa.txt
3.1.2 删除文件 【删】 rm
命令:rm -rf 文件名
3.1.3 修改文件【改】 vi或vim
【vi编辑器的3种模式】 基本上vi可以分为三种状态,分别是命令模式(command mode)、插入模式(Insert
mode)和底行模式(last line mode),各模式的功能区分如下: 1) 命令行模式command mode) 控制屏幕光标
的移动,字符、字或行的删除,查找,移动复制某区段及进入Insert mode下,或者到 last line mode。 命令行模式
下的常用命令: 【1】控制光标移动:↑,↓,j 【2】删除当前行:dd 【3】查找:/字符 【4】进入编辑模式:i o a
【5】进入底行模式::
2) 编辑模式(Insert mode) 只有在Insert mode下,才可以做文字输入,按「ESC」键可回到命令行模式。 编辑模
式下常用命令: 【1】ESC 退出编辑模式到命令行模式;
3) 底行模式(last line mode) 将文件保存或退出vi,也可以设置编辑环境,如寻找字符串、列出行号……等。 底行
模式下常用命令: 【1】退出编辑: :q 【2】强制退出: :q! 【3】保存并退出: :wq
打开文件命令:vi 文件名 示例:打开当前目录下的aa.txt文件 vi aa.txt 或者 vim aa.txt
注意:使用vi编辑器打开文件后,并不能编辑,因为此时处于命令模式,点击键盘i/a/o进入编辑模式。
编辑文件
使用vi编辑器打开文件后点击按键:i ,a或者o即可进入编辑模式。
i:在光标所在字符前开始插入 a:在光标所在字符后开始插入 o:在光标所在行的下面另起一新行插入
保存或者取消编辑
保存文件:
第一步:ESC 进入命令行模式 第二步:: 进入底行模式 第三步:wq 保存并退出编辑
取消编辑:
第一步:ESC 进入命令行模式 第二步:: 进入底行模式 第三步:q! 撤销本次修改并退出编辑
3.1.4 文件的查看【查】
文件的查看命令:cat/more/less/tail
cat:看最后一屏
示例:使用cat查看/etc/sudo.conf文件,只能显示最后一屏内容 cat sudo.conf
more:百分比显示
示例:使用more查看/etc/sudo.conf文件,可以显示百分比,回车可以向下一行,空格可以向下一页,q可以退出查
看 more sudo.conf
less:翻页查看
示例:使用less查看/etc/sudo.conf文件,可以使用键盘上的PgUp和PgDn向上 和向下翻页,q结束查看 less
sudo.conf
tail:指定行数或者动态查看
示例:使用tail -10 查看/etc/sudo.conf文件的后10行,Ctrl+C结束
tail -10 sudo.conf
3.2 权限修改 rwx:r代表可读,w代表可写,x代表该文件是一个可执行文件,如果rwx任意位置变为-则代表不可读
或不可写或不可执行文件。
示例:给aaa.txt文件权限改为可执行文件权限,aaa.txt文件的权限是-rw-------
第一位:-就代表是文件,d代表是文件夹 第一段(3位):代表拥有者的权限 第二段(3位):代表拥有者所在的
组,组员的权限 第三段(最后3位):代表的是其他用户的权限
421 421 421
rw- --- ---
命令:chmod +x aaa.txt 或者采用8421法 命令:chmod 100 aaa.txt
四、压缩文件操作
4.1 打包和压缩Windows的压缩文件的扩展名 .zip/.rar linux中的打包文件:aa.tar
linux中的压缩文件:bb.gz
linux中打包并压缩的文件:.tar.gz
Linux中的打包文件一般是以.tar结尾的,压缩的命令一般是以.gz结尾的。 而一般情况下打包和压缩是一起进行的,
打包并压缩后的文件的后缀名一般.tar.gz。
命令:tar -zcvf 打包压缩后的文件名 要打包的文件 其中:z:调用gzip压缩命令进行压缩 c:打包文件 v:显示运行
过程 f:指定文件名
示例:打包并压缩/usr/tmp 下的所有文件 压缩后的压缩包指定名称为xxx.tar tar -zcvf ab.tar aa.txt bb.txt 或:tar -
zcvf ab.tar *
4.2 解压
命令:tar [-zxvf] 压缩文件
其中:x:代表解压 示例:将/usr/tmp 下的ab.tar解压到当前目录下
示例:将/usr/tmp 下的ab.tar解压到根目录/usr下 tar -xvf ab.tar -C /usr------C代表指定解压的位置
五、查找命令
5.1 grep
grep命令是一种强大的文本搜索工具
使用实例:
ps -ef | grep sshd 查找指定ssh服务进程 ps -ef | grep sshd | grep -v grep 查找指定服务进程,排除gerp身 ps -ef |
grep sshd -c 查找指定进程个数 5.2 fifind fifind命令在目录结构中搜索文件,并对搜索结果执行指定的操作。
fifind 默认搜索当前目录及其子目录,并且不过滤任何结果(也就是返回所有文件),将它们全都显示在屏幕上。
使用实例:
fifind . -name ".log" -ls 在当前目录查找以.log结尾的文件,并显示详细信息。 fifind /root/ -perm 600 查找/root/目录下
权限为600的文件 fifind . -type f -name ".log" 查找当目录,以.log结尾的普通文件 fifind . -type d | sort 查找当前所有目
录并排序 fifind . -size +100M 查找当前目录大于100M的文件
十、其他命令
10.1 查看当前目录:pwd
命令:pwd 查看当前目录路径
10.2 查看进程:ps -ef
命令:ps -ef 查看所有正在运行的进程
10.3 结束进程:kill
命令:kill pid 或者 kill -9 pid(强制杀死进程) pid:进程号10.4 网络通信命令:
ifconfifig:查看网卡信息
命令:ifconfifig 或 ifconfifig | more
ping:查看与某台机器的连接情况
命令:ping ip
netstat -an:查看当前系统端口
命令:netstat -an
搜索指定端口 命令:netstat -an | grep 8080
10.5 配置网络
命令:setup
10.6 重启网络
命令:service network restart
10.7 切换用户
命令:su - 用户名
10.8 关闭防火墙
命令:chkconfifig iptables offff
或者:
iptables -L; iptables -F; service iptables stop 10.9 修改文件权限 命令:chmod 777
10.10 清屏
命令:ctrl + l
十一、高薪挑战宝典
1. Arraylist与LinkedList区别
可以从它们的底层数据结构、效率、开销进行阐述哈
ArrayList是数组的数据结构,LinkedList是链表的数据结构。
随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而ArrayList是基于索引(index)的数据结构,可以直接映射到。
插入、删除数据时,LinkedList的效率比较高,因为ArrayList要移动数据。
LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。
2. Collections.sort和Arrays.sort的实现原理
Collection.sort是对list进行排序,Arrays.sort是对数组进行排序。
Collections.sort底层实现
Collections.sort方法调用了list.sort方法list.sort方法调用了Arrays.sort的方法因此,Collections.sort方法底层就是调用的Array.sort方法
Arrays.sort底层实现
Arrays的sort方法,如下:如果比较器为null,进入sort(a)方法。如下:因此,Arrays的sort方法底层就是:
legacyMergeSort(a),归并排序,
ComparableTimSort.sort():即Timsort排序。
Timesort排序
Timsort排序是结合了合并排序(merge.sort)和插入排序(insertion sort)而得出的排序方法;
1.当数组长度小于某个值,采用的是二分插入排序算法,如下:
找到各个run,并入栈。
按规则合并run。
3. HashMap原理,java8做了什么改变
HashMap是以键值对存储数据的集合容器
HashMap是非线性安全的。
HashMap底层数据结构:数组+(链表、红黑树),jdk8之前是用数组+链表的方式实现,jdk8引进了红黑树
Hashmap数组的默认初始长度是16,key和value都允许null的存在
HashMap的内部实现数组是Node[]数组,上面存放的是key-value键值对的节点。HashMap通过put和get方法存储和获取。
HashMap的put方法,首先计算key的hashcode值,定位到对应的数组索引,然后再在该索引的单向链表上进行循环遍历,用equals比较key是否存在,如果存在则用新的value覆盖原值,如果没有则向后追加。
jdk8中put方法:先判断Hashmap是否为空,为空就扩容,不为空计算出key的hash值i,然后看table[i]是否为空,为空就直接插入,不为空判断当前位置的key和table[i]是否相同,相同就覆盖,不相同就查看table[i]是否是红黑树节点,如果是的话就用红黑树直接插入键值对,如果不是开始遍历链表插入,如果遇到重复值就覆盖,否则直接插入,如果链表长度大于8,转为红黑树结构,执行完成后看size是否大于阈值threshold,大于就扩容,否则直接结束。
Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。
HashMap的get方法就是计算出要获取元素的hash值,去对应位置获取即可。
HashMap的扩容机制,Hashmap的扩容中主要进行两步,第一步把数组长度变为原来的两倍,第二部把旧数组的元素重新计算hash插入到新数组中,jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二部一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
HashMap大小为什么是2的幂次方?效率高+空间分布均匀
有关于HashMap这些常量设计目的,也可以看我这篇文章:面试加分项-HashMap源码中这些常量的设计目的
4. List 和 Set,Map 的区别
List 以索引来存取元素,有序的,元素是允许重复的,可以插入多个null。
Set 不能存放重复元素,无序的,只允许一个null
Map 保存键值对映射,映射关系可以一对一、多对一
List 有基于数组、链表实现两种方式
Set、Map 容器有基于哈希存储和红黑树两种方式实现
Set 基于 Map 实现,Set 里的元素值就是 Map的键值
5. poll()方法和 remove()方法的区别?
Queue队列中,poll() 和 remove() 都是从队列中取出一个元素,在队列元素为空的情况下,remove() 方法会抛出异常,poll() 方法只会返回 null 。
看一下源码的解释吧:
/**
* Retrieves and removes the head of this queue. This method differs
* from {@link #poll poll} only in that it throws an exception if this
* queue is empty.
*
* @return the head of this queue
* @throws NoSuchElementException if this queue is empty
*/
E remove();
/**
* Retrieves and removes the head of this queue,
* or returns {@code null} if this queue is empty.
*
* @return the head of this queue, or {@code null} if this queue is empty
*/
E poll();
6. HashMap,HashTable,ConcurrentHash的共同点和区别
HashMap
底层由链表+数组+红黑树实现
可以存储null键和null值
线性不安全
初始容量为16,扩容每次都是2的n次幂
加载因子为0.75,当Map中元素总数超过Entry数组的0.75,触发扩容操作.
并发情况下,HashMap进行put操作会引起死循环,导致CPU利用率接近100%
HashMap是对Map接口的实现
HashTable
HashTable的底层也是由链表+数组+红黑树实现。
无论key还是value都不能为null
它是线性安全的,使用了synchronized关键字。
HashTable实现了Map接口和Dictionary抽象类
Hashtable初始容量为11
ConcurrentHashMap
ConcurrentHashMap的底层是数组+链表+红黑树
不能存储null键和值
ConcurrentHashMap是线程安全的
ConcurrentHashMap使用锁分段技术确保线性安全
JDK8为何又放弃分段锁,是因为多个分段锁浪费内存空间,竞争同一个锁的概率非常小,分段锁反而会造成效率低。
7. 写一段代码在遍历 ArrayList 时移除一个元素
因为foreach删除会导致快速失败问题,fori顺序遍历会导致重复元素没删除,所以正确解法如下:
第一种遍历,倒叙遍历删除
for(int i=list.size()-1; i>-1; i--){
if(list.get(i).equals("jay")){
list.remove(list.get(i));
}
}
第二种,迭代器删除
Iterator itr = list.iterator();
while(itr.hasNext()) {
if(itr.next().equals("jay") {
itr.remove();
}
}
8. Java中怎么打印数组?
数组是不能直接打印的哈,如下:
public class Test {
public static void main(String[] args) {
String[] jayArray = {"jay", "boy"};
System.out.println(jayArray);
}
}
//output
[Ljava.lang.String;@1540e19d
打印数组可以用流的方式Strem.of().foreach(),如下:
public class Test {
public static void main(String[] args) {
String[] jayArray = {"jay", "boy"};
Stream.of(jayArray).forEach(System.out::println);
}
}
//output
jay
boy
打印数组,最优雅的方式可以用这个APi,Arrays.toString()
public class Test {
public static void main(String[] args) {
String[] jayArray = {"jay", "boy"};
System.out.println(Arrays.toString(jayArray));
}
}
//output
[jay, boy]
9. TreeMap底层?
TreeMap实现了SotredMap接口,它是有序的集合。
TreeMap底层数据结构是一个红黑树,每个key-value都作为一个红黑树的节点。
如果在调用TreeMap的构造函数时没有指定比较器,则根据key执行自然排序。
10. HashMap 的扩容过程
Hashmap的扩容:
第一步把数组长度变为原来的两倍,
第二步把旧数组的元素重新计算hash插入到新数组中。
jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二步一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
11. HashSet是如何保证不重复的
可以看一下HashSet的add方法,元素E作为HashMap的key,我们都知道HashMap的可以是不允许重复的,哈哈。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
12. HashMap 是线程安全的吗,为什么不是线程安全的?死循环问题?
不是线性安全的。
并发的情况下,扩容可能导致死循环问题。
13. LinkedHashMap的应用,底层,原理
LinkedHashMap维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序(insert-order)或者是访问顺序,其中默认的迭代访问顺序就是插入顺序,即可以按插入的顺序遍历元素,这点和HashMap有很大的不同。
LRU算法可以用LinkedHashMap实现。
14. 哪些集合类是线程安全的?哪些不安全?
线性安全的
Vector:比Arraylist多了个同步化机制。
Hashtable:比Hashmap多了个线程安全。
ConcurrentHashMap:是一种高效但是线程安全的集合。
Stack:栈,也是线程安全的,继承于Vector。
线性不安全的
Hashmap
Arraylist
LinkedList
HashSet
TreeSet
TreeMap
15. ArrayList 和 Vector 的区别是什么?
Vector是线程安全的,ArrayList不是线程安全的。
ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。
Vector只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性。
16. Collection与Collections的区别是什么?
Collection是Java集合框架中的基本接口,如List接口也是继承于它
public interface List
Collections是Java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。如下:
public static
list.sort(null);
}
17. 如何决定使用 HashMap 还是TreeMap?
这个点,主要考察HashMap和TreeMap的区别。
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按key的升序排序,也可以指定排序的比较器。当用Iterator遍历TreeMap时,得到的记录是排过序的。
18. 如何实现数组和 List之间的转换?
List 转 Array
List 转Array,必须使用集合的 toArray(T[] array),如下:
List
list.add("jay");
list.add("tianluo");
// 使用泛型,无需显式类型转换
String[] array = list.toArray(new String[list.size()]);
System.out.println(array[0]);
如果直接使用 toArray 无参方法,返回值只能是 Object[] 类,强转其他类型可能有问题,demo如下:
List
list.add("jay");
list.add("tianluo");
String[] array = (String[]) list.toArray();
System.out.println(array[0]);
运行结果:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at Test.main(Test.java:14)
Array 转List
使用Arrays.asList() 把数组转换成集合时,不能使用修改集合相关的方法啦,如下:
String[] str = new String[] { "jay", "tianluo" };
List list = Arrays.asList(str);
list.add("boy");
运行结果如下:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at Test.main(Test.java:13)
因为 Arrays.asList不是返回java.util.ArrayList,而是一个内部类ArrayList。
可以这样使用弥补这个缺点:
//方式一:
ArrayList< String> arrayList = new ArrayList
Collections.addAll(arrayList, strArray);
//方式二:
ArrayList
19. 迭代器 Iterator 是什么?怎么用,有什么特点?
public interface Collection
Iterator
方法如下:
next() 方法获得集合中的下一个元素
hasNext() 检查集合中是否还有元素
remove() 方法将迭代器新返回的元素删除
forEachRemaining(Consumer super E> action) 方法,遍历所有元素
Iterator 主要是用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
使用demo如下:
List
Iterator
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
20. Iterator 和 ListIterator 有什么区别?
ListIterator 比 Iterator有更多的方法。
ListIterator只能用于遍历List及其子类,Iterator可用来遍历所有集合,
ListIterator遍历可以是逆向的,因为有previous()和hasPrevious()方法,而Iterator不可以。
ListIterator有add()方法,可以向List添加对象,而Iterator却不能。
ListIterator可以定位当前的索引位置,因为有nextIndex()和previousIndex()方法,而Iterator不可以。
ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改哦。
21. 怎么确保一个集合不能被修改?
很多朋友很可能想到用final关键字进行修饰,final修饰的这个成员变量,如果是基本数据类型,表示这个变量的值是不可改变的,如果是引用类型,则表示这个引用的地址值是不能改变的,但是这个引用所指向的对象里面的内容还是可以改变滴~验证一下,如下:
public class Test {
//final 修饰
private static final Map
{
map.put(1, "jay");
map.put(2, "tianluo");
}
public static void main(String[] args) {
map.put(1, "boy");
System.out.println(map.get(1));
}
}
运行结果如下:
//可以洗发现,final修饰,集合还是会被修改呢
boy
嘻嘻,那么,到底怎么确保一个集合不能被修改呢,看以下这三哥们~
unmodifiableMap
unmodifiableList
unmodifiableSet
再看一下demo吧
public class Test {
private static Map
{
map.put(1, "jay");
map.put(2, "tianluo");
}
public static void main(String[] args) {
map = Collections.unmodifiableMap(map);
map.put(1, "boy");
System.out.println(map.get(1));
}
}
运行结果:
// 可以发现,unmodifiableMap确保集合不能修改啦,抛异常了
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)
at Test.main(Test.java:14)
22. 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
快速失败
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
public class Test {
public static void main(String[] args) {
List
list.add(1);
list.add(2);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
list.add(3);
System.out.println(list.size());
}
}
}
运行结果:
1
Exception in thread "main" java.util.ConcurrentModificationException
3
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at Test.main(Test.java:12)
安全失败
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
public class Test {
public static void main(String[] args) {
List
list.add(1);
list.add(2);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
list.add(3);
System.out.println("list size:"+list.size());
}
}
}
运行结果:
1
list size:3
2
list size:4
其实,在java.util.concurrent 并发包的集合,如 ConcurrentHashMap, CopyOnWriteArrayList等,默认为都是安全失败的。
23. 什么是Java优先级队列(Priority Queue)?
优先队列PriorityQueue是Queue接口的实现,可以对其中元素进行排序
优先队列中元素默认排列顺序是升序排列
但对于自己定义的类来说,需要自己定义比较器
public class PriorityQueue
implements java.io.Serializable {
...
private final Comparator super E> comparator;
方法:
peek()//返回队首元素
poll()//返回队首元素,队首元素出队列
add()//添加元素
size()//返回队列元素个数
isEmpty()//判断队列是否为空,为空返回true,不空返回false
特点:
1.基于优先级堆
2.不允许null值
3.线程不安全
4.出入队时间复杂度O(log(n))
5.调用remove()返回堆内最小值
24. JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
jdk8 放弃了分段锁而是用了Node锁,减低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。
可以跟面试官聊聊悲观锁和CAS乐观锁的区别,优缺点哈~
25. 阻塞队列的实现,ArrayBlockingQueue的底层实现?
ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。常用的操作包括 add ,offer,put,remove,poll,take,peek。
可以结合线程池跟面试官讲一下哦~
26. Java 中的 LinkedList是单向链表还是双向链表?
哈哈,看源码吧,是双向链表
private static class Node
E item;
Node
Node
Node(Node
this.item = element;
this.next = next;
this.prev = prev;
}
}
27. 说一说ArrayList 的扩容机制吧
ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。
public boolean add(E e) {
//扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
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++;
// 如果最小需要空间比elementData的内存空间要大,则需要扩容
// overflow-conscious code
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);
}
28. HashMap 的长度为什么是2的幂次方,以及其他常量定义的含义~
为了能让HashMap存取高效,数据分配均匀。
看着呢,以下等式相等,但是位移运算比取余效率高很多呢~
hash%length=hash&(length-1)
可以看下我这篇文章哈~面试加分项-HashMap源码中这些常量的设计目的
29. ConcurrenHashMap 原理?1.8 中为什么要用红黑树?
聊到ConcurrenHashMap,需要跟面试官聊到安全性,分段锁segment,为什么放弃了分段锁,与及选择CAS,其实就是都是从效率和安全性触发,嘻嘻~
java8不是用红黑树来管理hashmap,而是在hash值相同的情况下(且重复数量大于8),用红黑树来管理数据。
红黑树相当于排序数据。可以自动的使用二分法进行定位。性能较高。
30. ArrayList的默认大小
ArrayList 的默认大小是 10 个元素
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
31. 为何Collection不从Cloneable和Serializable接口继承?
Collection表示一个集合,包含了一组对象元素。如何维护它的元素对象是由具体实现来决定的。因为集合的具体形式多种多样,例如list允许重复,set则不允许。而克隆(clone)和序列化(serializable)只对于具体的实体,对象有意义,你不能说去把一个接口,抽象类克隆,序列化甚至反序列化。所以具体的collection实现类是否可以克隆,是否可以序列化应该由其自身决定,而不能由其超类强行赋予。
如果collection继承了clone和serializable,那么所有的集合实现都会实现这两个接口,而如果某个实现它不需要被克隆,甚至不允许它序列化(序列化有风险),那么就与collection矛盾了。
32. Enumeration和Iterator接口的区别?
public interface Enumeration
boolean hasMoreElements();
E nextElement();
}
public interface Iterator
boolean hasNext();
E next();
void remove();
}
函数接口不同
Enumeration速度快,占用内存少,但是不是快速失败的,线程不安全。
Iterator允许删除底层数据,枚举不允许
Iterator安全性高,因为其他线程不能够修改正在被Iterator遍历的集合里面的对象。
33. 我们如何对一组对象进行排序?
可以用 Collections.sort()+ Comparator.comparing(),因为对对象排序,实际上是对对象的属性排序哈~
public class Student {
private String name;
private int score;
public Student(String name, int score){
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student: " + this.name + " 分数:" + Integer.toString( this.score );
}
}
public class Test {
public static void main(String[] args) {
List
studentList.add(new Student("D", 90));
studentList.add(new Student("C", 100));
studentList.add(new Student("B", 95));
studentList.add(new Student("A", 95));
Collections.sort(studentList, Comparator.comparing(Student::getScore).reversed().thenComparing(Student::getName));
studentList.stream().forEach(p -> System.out.println(p.toString()));
}
}
34. 当一个集合被作为参数传递给一个函数时,如何才可以确保函数不能修改它?
这个跟之前那个不可变集合一样道理哈~
在作为参数传递之前,使用Collections.unmodifiableCollection(Collection c)方法创建一个只读集合,这将确保改变集合的任何操作都会抛出UnsupportedOperationException。
35. 说一下HashSet的实现原理?
不能保证元素的排列顺序,顺序有可能发生变化。
元素可以为null
hashset保证元素不重复~ (这个面试官很可能会问什么原理,这个跟HashMap有关的哦)
HashSet,需要谈谈它俩hashcode()和equles()哦~
实际是基于HashMap实现的,HashSet 底层使用HashMap来保存所有元素的
看看它的add方法吧~
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
36. Array 和 ArrayList 有何区别?
定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。
ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。
37. 为什么HashMap中String、Integer这样的包装类适合作为key?
String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率~
因为
它们都是final修饰的类,不可变性,保证key的不可更改性,不会存在获取hash值不同的情况~
它们内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范
38. 如果想用Object作为hashMap的Key?;
重写hashCode()和equals()方法啦~ (这个答案来自互联网哈~)
重写hashCode()是因为需要计算存储数据的存储位置,需要注意不要试图从散列码计算中排除掉一个对象的关键部分来提高性能,这样虽然能更快但可能会导致更多的Hash碰撞;
重写equals()方法,需要遵守自反性、对称性、传递性、一致性以及对于任何非null的引用值x,x.equals(null)必须返回false的这几个特性,目的是为了保证key在哈希表中的唯一性;
39. 讲讲红黑树的特点?
每个节点或者是黑色,或者是红色。
根节点是黑色。
每个叶子节点(NIL)是黑色。[注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
如果一个节点是红色的,则它的子节点必须是黑色的。
从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
40. Java集合类框架的最佳实践有哪些?
其实这些点,结合平时工作,代码总结讲出来,更容易吸引到面试官呢 (这个答案来自互联网哈~)
1.根据应用需要正确选择要使用的集合类型对性能非常重要,比如:假如知道元素的大小是固定的,那么选用Array类型而不是ArrayList类型更为合适。
2.有些集合类型允许指定初始容量。因此,如果我们能估计出存储的元素的数目,我们可以指定初始容量来避免重新计算hash值或者扩容等。
3.为了类型安全、可读性和健壮性等原因总是要使用泛型。同时,使用泛型还可以避免运行时的ClassCastException。
4.使用JDK提供的不变类(immutable class)作为Map的键可以避免为我们自己的类实现hashCode()和equals()方法。
5.编程的时候接口优于实现
6.底层的集合实际上是空的情况下,返回为长度是0的集合或数组而不是null。
41.谈谈线程池阻塞队列吧~
ArrayBlockingQueue
LinkedBlockingQueue
DelayQueue
PriorityBlockingQueue
SynchronousQueue
ArrayBlockingQueue: (有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。
LinkedBlockingQueue: (可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列
DelayQueue:(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
PriorityBlockingQueue:(优先级队列)是具有优先级的无界阻塞队列;
SynchronousQueue:(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。针对面试题:线程池都有哪几种工作队列?
我觉得,回答以上几种ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue等,说出它们的特点,并结合使用到对应队列的常用线程池(如newFixedThreadPool线程池使用LinkedBlockingQueue),进行展开阐述, 就可以啦。
有兴趣的朋友,可以看看我的这篇文章哦~
面试必备:Java线程池解析
42. HashSet和TreeSet有什么区别?
Hashset 的底层是由哈希表实现的,Treeset 底层是由红黑树实现的。
HashSet中的元素没有顺序,TreeSet保存的元素有顺序性(实现Comparable接口)
HashSet的add(),remove(),contains()方法的时间复杂度是O(1);TreeSet中,add(),remove(),contains()方法的时间复杂度是O(logn)
43. Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()?
元素重复与否是使用equals()方法进行判断的,这个可以跟面试官说说==和equals()的区别,hashcode()和equals
44. 说出ArrayList,LinkedList的存储性能和特性
这道面试题,跟ArrayList,LinkedList,就是换汤不换药的~
ArrayList,使用数组方式存储数据,查询时,ArrayList是基于索引(index)的数据结构,可以直接映射到,速度较快;但是插入数据需要移动数据,效率就比LinkedList慢一点~
LinkedList,使用双向链表实现存储,按索引数据需要进行前向或后向遍历,查询相对ArrayList慢一点;但是插入数据速度较快。
LinkedList比ArrayList开销更大,因为LinkedList的节点除了存储数据,还需要存储引用。
45. HashMap在JDK1.7和JDK1.8中有哪些不同?
互联网上这个答案太详细啦(https://www.jianshu.com/p/939b8a672070)
46. ArrayList集合加入1万条数据,应该怎么提高效率
因为ArrayList的底层是数组实现,并且数组的默认值是10,如果插入10000条要不断的扩容,耗费时间,所以我们调用ArrayList的指定容量的构造器方法ArrayList(int size) 就可以实现不扩容,就提高了性能。
47. 如何对Object的list排序
看例子吧,哈哈,这个跟对象排序也是一样的呢~
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
List
list.add(new Person("jay", 18));
list.add(new Person("tianLuo", 10));
list.stream().forEach(p -> System.out.println(p.getName()+" "+p.getAge()));
// 用comparing比较对象属性
list.sort(Comparator.comparing(Person::getAge));
System.out.println("排序后");
list.stream().forEach(p -> System.out.print(p.getName()+" "+p.getAge()+" "));
}
}
48. ArrayList 和 HashMap 的默认大小是多数?
在 Java 7 中,ArrayList 的默认大小是 10 个元素,HashMap 的默认大小是16个元素(必须是2的幂)。
49. 有没有有顺序的Map实现类,如果有,他们是怎么保证有序的
Hashmap和Hashtable 都不是有序的。
TreeMap和LinkedHashmap都是有序的。(TreeMap默认是key升序,LinkedHashmap默认是数据插入顺序)
TreeMap是基于比较器Comparator来实现有序的。
LinkedHashmap是基于链表来实现数据插入有序的。
50. HashMap是怎么解决哈希冲突的
Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。
1、并发编程三要素?
1)原子性
原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。
2)可见性
可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
3)有序性
有序性,即程序的执行顺序按照代码的先后顺序来执行。
2、实现可见性的方法有哪些?
synchronized或者Lock:保证同一个时刻只有一个线程获取锁执行代码,锁释放之前把最新的值刷新到主内存,实现可见性。
3、多线程的价值?
1)发挥多核CPU的优势
多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的,采用多线程的方式去同时完成几件事情而不互相干扰。
2)防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
3)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
4、创建线程的有哪些方式?
1)继承Thread类创建线程类
2)通过Runnable接口创建线程类
3)通过Callable和Future创建线程
4)通过线程池创建
5、创建线程的三种方式的对比?
1)采用实现Runnable、Callable接口的方式创建多线程。
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2)使用继承Thread类的方式创建多线程
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
3)Runnable和Callable的区别
Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
Call方法可以抛出异常,run方法不可以。
运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
6、线程的状态流转图
线程的生命周期及五种基本状态:
7、Java线程具有五中基本状态
1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就
绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。
根据阻塞产生的原因不同,阻塞状态又可以分为三种:
a.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
b.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
c.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
8、什么是线程池?有哪几种创建方式?
线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。由于创建和销毁线程都是消耗系统资源的,所以当你想要频繁的创建和销毁线程的时候就可以考虑使用线程池来提升系统的性能。
java 提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。
9、四种线程池的创建:
1)newCachedThreadPool创建一个可缓存线程池
2)newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数。
3)newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4)newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务。
10、线程池的优点?
1)重用存在的线程,减少对象创建销毁的开销。
2)可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
3)提供定时执行、定期执行、单线程、并发数控制等功能。
11、常用的并发工具类有哪些?
CountDownLatch
CyclicBarrier
Semaphore
Exchanger
12、CyclicBarrier和CountDownLatch的区别
1)CountDownLatch简单的说就是一个线程等待,直到他所等待的其他线程都执行完成并且调用countDown()方法发出通知后,当前线程才可以继续执行。
2)cyclicBarrier是所有线程都进行等待,直到所有线程都准备好进入await()方法之后,所有线程同时开始执行!
3)CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
4)CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。如果被中断返回true,否则返回false。
13、synchronized的作用?
在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。
synchronized既可以加在一段代码上,也可以加在方法上。
14、volatile关键字的作用
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。
15、什么是CAS
CAS是compare and swap的缩写,即我们所说的比较交换。
cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加version来获取数据,性能较悲观锁有很大的提高。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。
java.util.concurrent.atomic 包下的类大多是使用CAS操作来实现的( AtomicInteger,AtomicBoolean,AtomicLong)。
16、CAS的问题
1)CAS容易造成ABA问题
一个线程a将数值改成了b,接着又改成了a,此时CAS认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version加1。在java5中,已经提供了AtomicStampedReference来解决问题。
2) 不能保证代码块的原子性
CAS机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
3)CAS造成CPU利用率增加
之前说过了CAS里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。
17、什么是Future?
在并发编程中,我们经常用到非阻塞的模型,在之前的多线程的三种实现中,不管是继承thread类还是实现runnable接口,都无法保证获取到之前的执行结果。通过实现Callback接口,并用Future可以来接收多线程的执行结果。
Future表示一个可能还没有完成的异步任务的结果,针对这个结果可以添加Callback以便在任务执行成功或失败后作出相应的操作。
18、什么是AQS
AQS是AbustactQueuedSynchronizer的简称,它是一个Java提高的底层同步工具类,用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态。
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如我们提到的ReentrantLock,Semaphore,其他的诸如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。
19、AQS支持两种同步方式:
1)独占式
2)共享式
这样方便使用者实现不同类型的同步组件,独占式如ReentrantLock,共享式如Semaphore,CountDownLatch,组合式的如ReentrantReadWriteLock。总之,AQS为使用提供了底层支撑,如何组装实现,使用者可以自由发挥。
20、ReadWriteLock是什么
首先明确一下,不是说ReentrantLock不好,只是ReentrantLock某些时候有局限。如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。
因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
21、FutureTask是什么
这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。
22、synchronized和ReentrantLock的区别
synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
2)ReentrantLock可以获取各种锁的信息
3)ReentrantLock可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word,这点我不能确定。
23、什么是乐观锁和悲观锁
1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
24、线程B怎么知道线程A修改了变量
volatile修饰变量
synchronized修饰修改变量的方法
wait/notify
while轮询
25、synchronized、volatile、CAS比较
synchronized是悲观锁,属于抢占式,会引起其他线程阻塞。
volatile提供多线程共享变量可见性和禁止指令重排序优化。
CAS是基于冲突检测的乐观锁(非阻塞)
26、sleep方法和wait方法有什么区别?
这个问题常问,sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器
27、ThreadLocal是什么?有什么用?
ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了。
28、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用
这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
29、多线程同步有哪几种方法?
Synchronized关键字,Lock锁实现,分布式锁等。
30、线程的调度策略
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运行:
1)线程体中调用了yield方法让出了对cpu的占用权利
2)线程体中调用了sleep方法使线程进入睡眠状态
3)线程由于IO操作受到阻塞
4)另外一个更高优先级线程出现
5)在支持时间片的系统中,该线程的时间片用完
31、ConcurrentHashMap的并发度是什么
ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?
32、Linux环境下如何查找哪个线程使用CPU最长
1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过
2)top -H -p pid,顺序不能改变
33、Java死锁以及如何避免?
Java中的死锁是一种编程情况,其中两个或多个线程被永久阻塞,Java死锁情况出现至少两个线程和两个或更多资源。
Java发生死锁的根本原因是:在申请锁时发生了交叉闭环申请。
34、死锁的原因
1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环。
例如:线程在获得了锁A并且没有释放的情况下去申请锁B,这时,另一个线程已经获得了锁B,在释放锁B之前又要先获得锁A,因此闭环发生,陷入死锁循环。
2)默认的锁申请操作是阻塞的。
所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。总之是尽量避免在一个同步方法中调用其它对象的延时方法和同步方法。
35、怎么唤醒一个阻塞的线程
如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。
36、不可变对象对多线程有什么帮助
前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。
37、什么是多线程的上下文切换
多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。
38、如果你提交任务时,线程池队列已满,这时会发生什么
这里区分一下:
1)如果使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务
2)如果使用的是有界队列比如ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy
39、Java中用到的线程调度算法是什么
抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
40、什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的优先级)。
41、什么是自旋
很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
42、Java
Concurrency API中的Lock接口(Lock
interface)是什么?对比同步它有什么优势?
Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有:
可以使锁更公平
可以使线程在等待锁的时候响应中断
可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
可以在不同的范围,以不同的顺序获取和释放锁
43、单例模式的线程安全性
老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:
1)饿汉式单例模式的写法:线程安全
2)懒汉式单例模式的写法:非线程安全
3)双检锁单例模式的写法:线程安全
44、Semaphore有什么作用
Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。
45、Executors类是什么?
Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。
Executors可以用于方便的创建线程池
46、线程类的构造方法、静态块是被哪个线程调用的
这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。
如果说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:
1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的
2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的
47、同步方法和同步块,哪个是更好的选择?
同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越小越好。
48、Java线程数过多会造成什么异常?
1)线程的生命周期开销非常高
2)消耗过多的CPU资源
如果可运行的线程数量多于可用处理器的数量,那么有线程将会被闲置。大量空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量的线程在竞争CPU资源时还将产生其他性能的开销。
3)降低稳定性
JVM在可创建线程的数量上存在一个限制,这个限制值将随着平台的不同而不同,并且承受着多个因素制约,包括JVM的启动参数、Thread构造函数中请求栈的大小,以及底层操作系统对线程的限制等。如果破坏了这些限制,那么可能抛出OutOfMemoryError异常。
1 事务
参考博文:https://www.cnblogs.com/kismetv/p/10331633.html
事务:事务是访问和更新数据库的程序执行的一个逻辑单元;事务中可能包含一个或多个sql语句,这些语句要么都执行,要么都不执行。作为一个关系型数据库,MySQL支持事务。
事务的特性(ACID):
原子性(Automicity):即整个事务是最小的一个执行单元,不可再分。事务的操作要么完成,要么都不做;如果事务中一个sql语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。
一致性(Consistency):即事务执行前后的状态变化是一致的。
隔离性(Isolation):隔离性是指事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(Durability):即事务执行完之后,结果是持久的,哪怕发生宕机,结果仍然是事务完成之后的状态
读操作可能会存在的问题:ACID事务特性,能够很好地保证单个事务的数据准确性。但是,在并发的情况下,多个事务共同操作一个数据库时,可能会产生脏读、不可重复读、幻读问题
脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。
不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。
幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。
事务的隔离级别:克服产生脏读、幻读等问题的方法是提高事务的隔离级别。SQL标准中定义了四种隔离级别,一般来说,隔离级别越低,开销越低,可支持并发性越高,但隔离性越差。隔离级别与会产生的问题如下:
读未提交隔离级别最低、可支持并发度最高,无法克服脏读、不可重复读和幻读任何一种问题;读已提交可以克服脏读现象
可重复读克服脏读和不可重复读;可串行化(序列化)可以克服全部三种问题,但是可串行化强制事务串行,并发效率很低,只有当对数据一致性要求极高且可以接受没有并发时使用,因此使用也较少。因此在大多数数据库系统中,默认的隔离级别是读已提交(RC:ORACLE,SQLSERVER)或可重复读(RR:MYSQL)
现在互联网工程一般默认选择RC,主要原因有:
1 在RR隔离级别下,存在间隙锁,导致出现死锁的几率比RC大的多!
此时执行语句“select * from test where id <3 for update” ,在RR隔离级别下,存在间隙锁,可以锁住(2,5)这个间隙,防止其他事务插入数据!而在RC隔离级别下,不存在间隙锁,其他事务是可以插入数据!
2 在RR隔离级别下,条件列未命中索引会锁表!而在RC隔离级别下,只锁行。
在RC隔离级别下,其先走聚簇索引,进行全部扫描加锁,但是MySQL做了优化,在MySQL Server过滤条件,发现不满足后,会调用unlock_row方法,把不满足条件的记录放锁。而在RR隔离级别下,会直接把整张表加锁。
MVCC(Multi-Version Concurrency Control,即多版本的并发控制协议):在Mysql中,通过MVCC来利用RR解决脏读、不可重复读、幻读等问题。
数据的读取可以分为两种:快照读和当前读。快照读适用于简单的select语句,当前读是基于临键锁(行锁 + 间歇锁)来实现的,适用于 insert,update,delete, select ... for update, select ... lock in share mode 语句,以及加锁了的 select 语句
mysql默认是快照读
出现(delete、update、insert)时改为当前读,最新的数据也会被读取。
第一步:mysql会为每一条数据,隐式加上两个字段,一个是创建版本号赋值,另一个是删除版本号赋值。在快照读的状态下,表的数据发生变化即会制作成一个新的版本。select时读取数据的规则为:创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。通过MVCC机制,虽然让数据变得可重复读,但我们读到的数据可能是历史数据,不是数据库最新的数据。这种读取历史数据的方式,我们叫它快照读 (snapshot read),而读取数据库最新版本数据的方式,叫当前读 (current read)。
第二步:locks由record locks(行锁、索引加锁) 和 gap locks(间隙锁),读取数据的附近记录也加锁,保证能够读取到最新数据,附近数据不被修改
2 B树和B+树
参考博文:程序员小灰有关B树和B+树的讲解
这里之所以提到B树和B+树,是因为mysql里面最常用的索引就是B+树,有时候面试会问到为什么使用B+树,B+树有什么优势。
B数和B+树简单来说是一种多路搜索树。
B树:一个m阶的B树具有如下几个特征:
根结点至少有两个子女。
每个中间节点都包含k-1个元素和k个孩子,其中 m/2 <= k <= m
每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m
所有的叶子结点都位于同一层。
每个节点中的元素从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域分划。
如图,是一个三阶的B树
B树的关键字分布在整棵树中,任何关键字出现且仅出现一次在一个节点中,其查找复杂度相当于一个二分查找
B+树:B+树是B树的一种变体,有着比B树更好的查询性能。对于一个B+树,除了B树的特点之外,还有如下特点:
有k个子树的中间节点包含有k个元素(B树中是k-1个元素),每个元素不保存数据,只用来索引,所有数据都保存在叶子节点(这就是所谓的卫星数据。卫星数据就是指节点的具体信息)。
所有的叶子结点中包含了全部元素的信息,及指向含这些元素记录的指针,且叶子结点本身依关键字的大小自小而大顺序链接。
每一个父节点都出现在子节点中,是子节点元素中是最大(或最小)元素。
如图,一个三阶B+树
B+树的优势:
B+树的中间节点没有卫星数据,所以同样大小的磁盘页可以容纳更多的节点元素,使得查询的IO次数更少。
所有查询都要查找到叶子节点,查询性能稳定。
所有叶子节点形成有序链表,范围查询只需在链表上做遍历即可,便于范围查询。
hash索引和B+树索引区别:Mysql里面一共有四种索引,经常会问的索引除了B+数之外,还有hash索引(hash索引就是采用一定的hash算法建立索引,键值对)
如果是等值查询,那么哈希索引明显有绝对优势,只需要经过一次算法即可找到相应的键值;
如果是范围查询检索,原先是有序的键值,经过哈希算法后,有可能变成不连续的了,不能利用索引完成范围查询检索;
哈希索引也没办法利用索引完成排序,以及like ‘xxx%’ 这样的部分模糊查询
哈希索引也不支持复合索引的最左匹配规则;
B+树索引的关键字检索效率比较平均,不像B树那样波动幅度大,在有大量重复键值情况下,因为哈希碰撞问题,哈希索引的效率也会变低。
3 建立索引
索引:索引是帮助Mysql高效获取数据的数据结构。常用的索引有主键索引、普通索引、唯一索引和全文索引。
主键索引(PRIMARY ):唯一且不能为空,在一个表里面,主键索引也就是这个表的主键。
普通索引(INDEX):普通的索引。
唯一索引(UNIQUE):唯一索引是普通索引的特殊情况,索引不允许有重复,主键索引就是一种唯一索引。
全文索引(FULLTEXT ):用于在一篇文章中,检索文本信息的。
什么情况建立索引:
适合创建索引条件
主键自动建立主键索引
频繁作为查询条件的字段应该建立索引
查询中与其他表关联的字段,外键关系建立索引
单键/组合索引的选择问题,组合索引性价比更高
查询中排序的字段,排序字段若通过索引去访问将大大提高排序效率
查询中统计或者分组字段
不适合创建索引条件
数据量不大的
经常增删改的表或者字段
where条件里用不到的字段不创建索引
数据存在大量重复的
优势:提高数据检索的效率,降低数据库IO成本
劣势:索引也需要维护
4 组合索引(多列索引)
组合索引:MySQL能在多个列上创建索引。一个索引可以由最多15个列组成,组合索引的性价比相对来说更高。
最左原则:组合索引是先按照第一列进行排序,然后在第一列排好序的基础上再对第二列排序,如果跳过第一列直接访问第二列,直接访问后面的列就用不到索引了。例如,组合索引(a,b,c),一般都是先匹配a,然后匹配b,最后匹配c。
适用场景:
全字段匹配
匹配部分最左前缀
匹配第一列范围查询(可用用like a%,但不能使用like %b,最左原则)
精确匹配某一列和和范围匹配另外一列
索引失效的几种情况:
使用like '% '进行查询模糊查询
组合索引不符合最左匹配原则
使用了or关键字
where之后的使用了函数
mysql内部有一个优化器,在进行查询的时候,会把使用普通索引、主键索引、全表扫描的消耗都计算出来选择最优的方法,在某些情况下,全表扫描的性能更优就会出现索引失效。
5 聚簇索引和非聚簇索引(针对B+树索引)
无论是聚簇索引还是非聚簇索引,都不是一种单独的数据结构,而是一种数据存储方式。
聚簇索引:聚簇索引的叶子节点就是数据节点。在InnoDB引擎就是聚簇索引,聚簇索引默认是主键(如果表中没有定义主键,InnoDB会选择一个唯一的非空索引代替,也可以自己设置聚簇索引),一张表内只能有一个聚簇索引,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据。
非聚簇索引:非聚簇索引(主要是为了区别聚簇索引,MyISAM引擎用的就是非聚簇索引)的叶子节点仍然是索引节点,只不过有指向对应数据块的指针。
优点:聚簇索引数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中;
缺点:聚簇索引更新代价特别高。
6 数据库引擎(主要就是MyISAM和InnoDB的区别)
区别
1. InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
2. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
3. InnoDB是聚集索引,使用B+Tree作为索引结构,数据文件是和(主键)索引绑在一起的(表数据文件本身就是按B+Tree组织的一个索引结构),必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
MyISAM是非聚集索引,也是使用B+Tree作为索引结构,索引和数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
也就是说:InnoDB的B+树主键索引的叶子节点就是数据文件,辅助索引的叶子节点是主键的值;而MyISAM的B+树主键索引和辅助索引的叶子节点都是数据文件的地址指针。(具体参考上面的聚簇索引和非聚簇索引)
4. InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快(注意不能加有任何WHERE条件);
那么为什么InnoDB没有了这个变量呢?
因为InnoDB的事务特性,在同一时刻表中的行数对于不同的事务而言是不一样的,因此count统计会计算对于当前事务而言可以统计到的行数,而不是将总行数储存起来方便快速查询。InnoDB会尝试遍历一个尽可能小的索引除非优化器提示使用别的索引。如果二级索引不存在,InnoDB还会尝试去遍历其他聚簇索引。
如果索引并没有完全处于InnoDB维护的缓冲区(Buffer Pool)中,count操作会比较费时。可以建立一个记录总行数的表并让你的程序在INSERT/DELETE时更新对应的数据。和上面提到的问题一样,如果此时存在多个事务的话这种方案也不太好用。如果得到大致的行数值已经足够满足需求可以尝试SHOW TABLE STATUS
5. Innodb不支持全文索引,而MyISAM支持全文索引,在涉及全文索引领域的查询效率上MyISAM速度更快高;PS:5.7以后的InnoDB支持全文索引了
6. MyISAM表格可以被压缩后进行查询操作
7. InnoDB支持表、行(默认)级锁,而MyISAM支持表级锁
InnoDB的行锁是实现在索引上的,而不是锁在物理行记录上。潜台词是,如果访问没有命中索引,也无法使用行锁,将要退化为表锁。
t_user(uid, uname, age, sex) innodb;
uid PK
无其他索引
update t_user set age=10 where uid=1; 命中索引,行锁。
update t_user set age=10 where uid != 1; 未命中索引,表锁。
update t_user set age=10 where name='chackca'; 无索引,表锁。
8、InnoDB表必须有唯一索引(如主键)(用户没有指定的话会自己找/生产一个隐藏列Row_id来充当默认主键),而Myisam可以没有
9、Innodb存储文件有frm、ibd,而Myisam是frm、MYD、MYI
Innodb:frm是表定义文件,ibd是数据文件
Myisam:frm是表定义文件,myd是数据文件,myi是索引文件
如何选择:
1. 是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM;
2. 如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读也有写,请使用InnoDB。
3. 系统奔溃后,MyISAM恢复起来更困难,能否接受;
4. MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。
InnoDB为什么推荐使用自增ID作为主键?
答:自增ID可以保证每次插入时B+索引是从右边扩展的,可以避免B+树和频繁合并和分裂(对比使用UUID)。如果使用字符串主键和随机主键,会使得数据随机插入,效率比较差。
7 主从复制(读写分离、数据备份)
概念: mysql主从分离其实也就是读写分离,将读操作和协操作分别导入到不同的服务器集群;
原理: 主从分离是如何工作的
在主从分离里面有主服务器(master)和从服务器(slaver),如图,其工作步骤主要分为三步:
首先主服务器(master)将对数据的操作都记录到二进制日志(binary log)中。
从服务器(slaver)将binary log拷贝到其中继日志(Relay log)中
slaver重做Relay log里面的事件,更新slaver里面的数据与master达到数据一致。
binary log有三种形式,分别是:
statement:记录的是修改SQL语句
row:记录的是每行实际数据的变更
mixed:statement和row模式的混合
Mysql在5.0这个版本以前,binlog只支持STATEMENT这种格式!而这种格式在读已提交(Read Commited)这个隔离级别下主从复制是有bug的,因此Mysql将可重复读(Repeatable Read)作为默认的隔离级别!
7 分库、分表、分区
8 SQL优化:
在说mysql优化之前,首先谈一谈explain关键字,如果面试的时候说mysql优化提到了explain关键字可能会给面试官你是真的做过mysql优化的感觉。
explain:explain被称为执行计划,如果在select语句前放上关键词explain,mysql将解释它如何处理select,提供有关表如何联接和联接的次序。
EXPLAIN SELECT * FROM tb_area,tb_shop WHERE tb_area.area_id=tb_shop.area_id
在select语句前面加 EXPLAIN 关键字,会出现如下
explain属性:
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
id
1. id相同时,执行顺序由上至下
2. 如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
3.id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行
select_type 查询中每个select子句的类型
(1) SIMPLE(简单SELECT,不使用UNION或子查询等)
(2) PRIMARY(查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
(3) UNION(UNION中的第二个或后面的SELECT语句)
(4) DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
(5) UNION RESULT(UNION的结果)
(6) SUBQUERY(子查询中的第一个SELECT)
(7) DEPENDENT SUBQUERY(子查询中的第一个SELECT,取决于外面的查询)
(8) DERIVED(派生表的SELECT, FROM子句的子查询)
(9) UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
Table:输出行所引用的表
表示MySQL在表中找到所需行的方式,又称“访问类型”。
常用的类型有: ALL, index, range, ref, eq_ref, const, system, NULL(从左到右,性能从差到好)
ALL:Full Table Scan, MySQL将遍历全表以找到匹配的行
index: Full Index Scan,index与ALL区别为index类型只遍历索引树
range:只检索给定范围的行,使用一个索引来选择行
ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量system是const类型的特例,当查询的表只有一行的情况下,使用system
NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
possible_keys : 指出能在该表中使用哪些索引有助于 查询。如果为空,说明没有可用的索引。
key:实际从 possible_key 选择使用的索引。 如果为 NULL,则没有使用索引。很少的情况 下,MYSQL 会选择优化不足的索引。这种情 况下,可以在 SELECT 语句中使用 USE INDEX (indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制 MYSQL 忽略索引
key_len: 使用的索引的长度。在不损失精确性的情况 下,长度越短越好。
ref: 显示索引的哪一列被使用了
rows: 认为必须检查的用来返回请求数据的行数
extra中出现以下 2 项意味着 根本不能使用索引,效率会受到重大影响。应尽可能对此进行优化。
我们可以利用EXPLAIN关键字来分析一个SELECT语句的执行情况。
总的来说,SQL优化的原则有三点:1,尽量避免放弃索引而导致全表扫描;2 避免使用select *返回多余数据;3 合理建立索引
优化方式如下:
在表中建立索引,优先考虑where、group by使用到的字段。
尽量避免使用select *,返回无用的字段会降低查询效率。如下:SELECT * FROM t
优化方式:使用具体的字段代替*,只返回使用到的字段。
尽量避免使用in 和not in,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE id IN (2,3)
SELECT * FROM t1 WHERE username IN (SELECT username FROM t2)
优化方式:如果是连续数值,可以用between代替。如下:
SELECT * FROM t WHERE id BETWEEN 2 AND 3
如果是子查询,可以用exists代替。如下:
SELECT * FROM t1 WHERE EXISTS (SELECT * FROM t2 WHERE t1.username = t2.username)
尽量避免使用or,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE id = 1 OR id = 3
优化方式:可以用union代替or。如下:
SELECT * FROM t WHERE id = 1
UNION
SELECT * FROM t WHERE id = 3
(PS:如果or两边的字段是同一个,如例子中这样。貌似两种方式效率差不多,即使union扫描的是索引,or扫描的是全表)
尽量避免在字段开头模糊查询,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE username LIKE '%li%'
优化方式:尽量在字段后面使用模糊查询。如下:
SELECT * FROM t WHERE username LIKE 'li%'
尽量避免进行null值的判断,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE score IS NULL
优化方式:可以给字段添加默认值0,对0值进行判断。如下:
SELECT * FROM t WHERE score = 0
尽量避免在where条件中等号的左侧进行表达式、函数操作,会导致数据库引擎放弃索引进行全表扫描。如下:
SELECT * FROM t2 WHERE score/10 = 9
SELECT * FROM t2 WHERE SUBSTR(username,1,2) = 'li'
优化方式:可以将表达式、函数操作移动到等号右侧。如下:
SELECT * FROM t2 WHERE score = 10*9
SELECT * FROM t2 WHERE username LIKE 'li%'
当数据量大时,避免使用where 1=1的条件。通常为了方便拼装查询条件,我们会默认使用该条件,数据库引擎会放弃索引进行全表扫描。如下:
SELECT * FROM t WHERE 1=1