【JAVA Core】精品面试题100道

【JAVA Core】精品面试题100道

加个说明:我的初心是Java每个技术栈整理个100道面试题,现在的底子是哪吒的《208道面试题》
后续我会把自己有价值的题和面试真题添加进入,也对一部分题的错误之处做了重新的修改
反正不会用于盈利目的,整理和自我学习,有取自别人的地方,请理解也心存感激   
                                                                   --阿呆布衣酷

1.HashMap和ConcurrentHashMap的区别是什么?
2.==和equals()方法的区别是什么?
3.final在Java中有什么作用?
4.Java中的Math.round(-1.5)等于多少?
5.String属于基本数据类型吗?
6.String str="i"与String str=new String(“i”)一样吗?
7.如何将字符串反转?
8. String类的常用方法都有哪些?
9.new String(“a”)+new String(“b”)会创建几个对象?
10.普通类和抽象类有哪些区别?

11.接口和抽象类有什么区别?
12.HashMap如果想要自己的Object作为Key应该怎么办?
13.BIO、NIO、AIO有什么区别?
14.Files的常用方法有哪些?
15.什么是反射?
16.什么是Java序列化?什么情况下需要序列化?
17.为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?
18.throw和throws的区别?
19.final、finally、finalize有什么区别?
20.try-catch-finally中,如果catch中return了,finally还会执行吗?

21.常见的异常类有哪些?
22.hashCode()是什么方法?有什么作用?
23.Java中操作字符串都有哪些类?它们之间有什么区别?
24.Java中都有哪些引用类型?
25.在Java中,为什么不允许从静态方法中访问非静态变量?
26.说说JavaBean的命名规范
27.JavaBean属性命名规范问题分析?
28.什么是Java的内存模型?
29.举例说明什么情况下会更倾向于使用抽象类而不是接口?
30.实例化对象有哪几种方式?

31.byte类型127+1等于多少?
32.Java容器都有哪些?
33.Collection和Collections有什么区别?
34.List和Set区别?
35.HashMap和Hashtable有什么区别?
36.说一下HashMap的实现原理?
37.Set有哪些实现类?
38.说一下HashSet的实现原理
39.ArrayList和LinkedList的区别是什么?
40.如何实现数组和List集合之间的转换?

41.在Queue中poll()和remove()有什么区别?
42.哪些集合类是线程安全的?
43.迭代器Iterator是什么?
44.Iterator怎么使用?有什么特点?
45.Iterator和ListIterator有什么区别?
46.怎么确保一个集合不能被修改?
47.队列和栈是什么?有什么区别?
48.Java8开始ConcurrentHashMap为什么舍弃分段锁?
49.ConcurrentHashMap(JDK8)为什么要使用synchronized而不是如ReentranLock这样的可重入锁?
50.ConcurrentHashMap和HashTable有什么区别?

51.HashMap和HashSet的区别是什么?
52.请谈谈ReadWriteLock和StampedLock
53.线程的run()和start()有什么区别?
54.为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
55.Synchronized用过吗?其原理是什么?
56.JVM对Java的原生锁做了哪些优化?
57.为什么wait(),notify()和notifyAll()在Object类中?
58.Java如何实现多线程之间的通讯和协作?
59.Thread类中的yield()方法有什么作用?
60.为什么说Synchronized是非公平锁?

61.请谈谈volatile有什么特点,为什么它能保证变量对所有线程的可见性?
62.为什么说Synchronized是一个悲观锁?乐观锁的实现原理又是什么?什么是CAS?它有什么特性?
63.乐观锁一定就是好的吗?
64.请尽可能详尽地对比Synchronized和ReentrantLock的异同
65.ReentrantLock是如何实现可重入性的?
66. 什么是锁消除和锁粗化?
67.跟Synchronized相比,可重入锁ReentrantLock其实现原理有什么不同?
68.那么请谈谈AQS框架是怎么一回事?
69.AQS对资源的共享方式?
70.如何让Java的线程彼此同步?

71.你了解过哪些同步器?请分别介绍下
72.Java中的线程池是如何实现的?
73.创建线程池的几个核心构造参数?
74.线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?
75.volatile关键字的作用是什么?
76.既然volatile能够保证线程间的变量可见性,是不是就意味着基于volatile变量的运算就是并发安全的?
77.ThreadLocal是什么?有哪些使用场景?
78.请谈谈ThreadLocal是怎么解决并发安全的?
79.很多人都说要慎用ThreadLocal,谈谈你的理解,使用ThreadLocal需要注意些什么?
80.为什么代码会重排序?

81.什么是自旋?
82.多线程中synchronized锁升级的原理是什么?
83.synchronized和ReentrantLock区别是什么?
84.Java Concurrency API中的Lock接口(Lock Interface)是什么?对比同步它有什么优势?
85.Java中什么时候用重载?什么时候用重写?
86.简述Collections.sort()排序内部原理?
87.String和StringBuilder有什么区别?
88.Vector与ArrayList有哪些区别?
89.HashMap与HashTable有什么区别?
90.ArrayList与LinkedList有什么区别?

91.Comparable与Comparator有什么不同?
92.Collection与Collections的区别是什么?
93.Java中多态的实现原理?
94.Object中定义了哪些方法?
95.Java泛型和类型擦除?
96.说出5个JDK8引入的新特性
97.常用的数据结构有哪些?
98.Java中的TreeMap是采用什么树实现的?
99.匿名内部类是什么?如何访问在其外面定义的变量?
100.写一段代码遍历ArrayList移除一个元素

1.HashMap和ConcurrentHashMap的区别是什么?

HashMapConcurrentHashMap都是Java中的Map接口的实现类,但是它们之间有以下几点不同:
1.线程安全性:
HashMap是非线程安全的,而ConcurrentHashMap是线程安全的。
2.主要实现方式:
HashMap通过使用数组和链表的方式实现,
而ConcurrentHashMap则是通过分段锁的方式实现。

3.迭代器的一致性:
在HashMap中,如果在迭代Map的过程中对Map进行了修改,会抛出ConcurrentModificationException异常,
而ConcurrentHashMap则不会抛出该异常,因为它能够保证迭代器的一致性。

4.性能:
在并发访问的情况下,ConcurrentHashMap的性能比HashMap要好,因为它使用分段锁,不同的线程可以同时访问不同的段,从而提高了并发访问的效率。

多线程环境下使用Map,建议使用ConcurrentHashMap,
如果在单线程环境下使用,可以选择HashMap

2.==和equals()方法的区别是什么?

对于基本数据类型
==比较的是值
equals()方法不能用于基本数据类型比较

对于引用数据类型
==比较的是地址
如果没有重写equals()方法,equals()方法相当于==
如果重写了equals()方法,equals()方法比较对象的内容

3.final在Java中有什么作用?

final修饰类
当final修饰类,该类成为最终类,无法被继承
比如常用的String类就是最终类

final修饰方法
当final修饰方法,该方法成为最终方法,无法被子类重写
但是该方法仍然可以被继承

final修饰变量
1.final修饰基本数据类型,该变量为常量,该值一旦被赋值无法再修改
2.final修饰引用数据类型,比如对象、数组,
则该对象、数组本身内容可以修改,
但指向该对象或数组的地址引用不能修改
3.final修饰类的成员变量,必须赋值,否则编译报错

4.Java中的Math.round(-1.5)等于多少?

Math.ceil():向上取整
Math.ceil(11.3)=12
Math.ceil(-11.3)=-11

Math.floor():向下取整
Math.floor(11.3)=11
Math.floor(-11.3)=-12

Math.round:四舍五入
即先加 0.5 然后向下取整
Math.round(11.3)=11
Math.round(11.8)=12
Math.round(-11.3)=-11
Math.round(-11.8)=-12

5.String属于基本数据类型吗?

不属于
基本数据类型有八种:
byte  short  int  long 
float  double 
char boolean

6.String str="i"与String str=new String(“i”)一样吗?

String str="i"  会先在常量池中寻找i,
如果常量池中存在i,就将i的地址赋给变量str
如果常量池中不存在i,就创建一个i再将新创建的地址赋给变量str

String str=new String("i")
会将对象分配到堆空间中,即使内容一样也会重新创建新的对象

7.如何将字符串反转?

将字符串作为参数生成StringBuilder对象
然后stringBuilder.reverse()方法实现反转
即通过stringBuilder对象调用reverse()方法实现

public class Test{
	public static void main(String[] args){
		String str = "abcdefg";
		StringBuilder stringBuilder = new StringBuilder(str);
		String ret = stringBuilder.reverse().toString();
		System.out.println(ret);		
	}
}

8. String类的常用方法都有哪些?

一:常见String类的获取功能
length():获取字符串的长度
charAt(int index):获取指定索引位置的字符
indexOf(char ch):获取指定字符在该字符串中第一次出现的索引位置
substring(int start):从指定位置开始截取字符串,默认到末尾
substring(int start,int end):截取字符串从指定位置开始到指定位置结束

二:常见String类的判断功能
equals(Object obj):判断字符串的内容是否相同,区分大小写
contains(String str):判断字符串中是否包含指定字符串
startsWith(String str):判断字符串是否以指定字符串开头
endsWith(String str):判断字符串是否以指定字符串结尾
isEmpty():判断字符串内容是否是空字符串""

三:常见String类的转换功能
byte[] getBytes():将字符串转换为字节数组
char[] toCharArray():将字符串转换为字符数组
String valueOf(char[] chars):将字符数组转换为字符串,valueOf()方法可以将任意类型转换为字符串
toLowerCase():把字符串的英文字母都转换成小写字母
toUpperCase():把字符串的英文字母都转换成大写字母
concat(String str):拼接字符串

四:常见String类的其它功能
replace(char old,char new):将指定旧字符替换为新字符
replace(String old,String new):将指定旧字符串替换为新字符串
trim():去除两端空格
int compartTo(String str):
首先根据ASCII表,从第一个字母进行减法运算,返回的就是这个减法的结果
如果前面几个字母完全一样接着会根据两个字符串的长度进行减法运算,返回的就是这个减法的结果
如果两个字符串完全一样,返回的就是0

9.new String(“a”)+new String(“b”)会创建几个对象?

对象1new StringBuilder()
底层拼接字符串会创建一个 StringBuilder 对象

对象2: new String("a")

对象3:在字符串常量池中放入"a"(如果之前字符串常量池中没有"a"的话)

对象4new String("b")

对象5:在字符串常量池中放入"b"(如果之前字符串常量池中没有"b"的话)

对象6:调用stringBuilder.toString()方法,会new StringBuilder("ab")

特别强调:stringBuilder.toString()的调用,在字符串常量池中没有生成"ab"

//附加题:
public class Test {
	public static void main(String[] args) {
		String s1 = new String("1") + new String("1");//s1变量记录的地址为:new String
		s1.intern();//在字符串常量池中生成"11"。如何理解:jdk6:创建了一个新的对象"11",也就有新的地址;jdk7:并没有创建新的对象"11"
		String s2 = "11";
		System.out.println(s1 == s2);//jdk6:false;jdk7:true
	}
}

10.普通类和抽象类有哪些区别?

一:是否可以实例化
普通类可以实例化
抽象类不能实例化

二:是否有抽象方法
普通类没有抽象方法
有抽象方法的类一定是抽象类,抽象类可以有抽象方法,只需申明,无需实现
抽象类的子类必须实现抽象类中的所有抽象方法,否则子类仍然是抽象类

三:方法的修饰符方面,抽象方法不能被staticfinal修饰
抽象方法不能被static修饰
因为抽象方法没有方法体,如果是静态的,可以类直接调用方法,这种调用没有意义
抽象方法不能被final修饰
抽象类的子类必须实现抽象类中的所有抽象方法,而被final修饰的方法不能被重写

11.接口和抽象类有什么区别?

一:修饰符方面
 接口用interface修饰符修饰
 抽象类用abstract修饰符修饰
 
 二:继承方面
 类可以实现多个接口
 而抽象类只能单继承
 
 三:方法体方面
 接口:JDK8之前,接口中的方法都是抽象方法,没有方法体,默认省略了public abstract修饰符
       JDK8之后,接口中可以定义静态方法,静态方法必须有方法体
	             接口中普通方法没有方法体,必须被实现

 抽象类:可以包含抽象方法和非抽象方法,非抽象方法必须有方法体
 
 四:子类方面
 实现接口的子类必须实现全部接口的全部抽象方法
 
 如果一个类继承了抽象类:
 1.如果实现了所有的抽象方法,子类可以不是抽象类
 2.如果没有实现所有的抽象方法,子类仍然是抽象类

五:设计理念
抽象类表示"is-a"关系
接口表示"like-a"关系

六:实现类中变量是否可以重新定义
抽象类中的变量默认是friendly型,即默认的。其值可以在子类中重新定义和赋值
接口中变量默认是public static final型,且必须给其初值,所以实现类中不能重新定义,也不能改变其值

七:可见性
抽象类子类实现父类中抽象方法时,可见性可以大于等于父类
接口实现类中的接口方法的可见性只能与接口中相同(public)

八:目的
用抽象类是为了代码复用
用接口是为了制定规范

12.HashMap如果想要自己的Object作为Key应该怎么办?

如果想让自定义的对象作为HashMapKey,需要满足两个条件:

1.重写hashCode方法:hashCode方法返回的是对象的散列值,HashMap使用散列表来存储键值对,因此需要通过hashCode方法来计算对象的散列值,以决定对象在散列表中的位置。

2.重写equals方法:equals方法用于比较两个对象是否相等,在HashMap中,如果两个Key的hashCode值相等,就会调用equals方法来判断它们是否相等,如果相等,就认为这两个Key相等。

另外,还需要注意的是,如果自定义的对象作为Key的时候,如果它的属性值发生了变化,那么它的hashCode值也会发生变化,这可能会导致该对象在HashMap中无法找到。因此,在自定义对象作为Key的时候,需要确保该对象的属性值在使用时不会发生变化,或者使用不可变对象作为Key

13.BIO、NIO、AIO有什么区别?

一:BIO:同步阻塞
BIO:一次连接一个线程
JDK 1.4之前,建立网络连接的时候采用BIO模式,
先启动服务端socket,然后启动客户端socket,
对服务端通信,客户端发送请求后,先判断服务端是否有线程响应,
如果没有则会一直等待或者遭到拒绝请求,
如果有的话会等待请求结束后继续执行

二:NIO:同步非阻塞
NIO主要是想解决BIO的大并发问题,BIO是每一个请求分配一个线程,一次连接有多个请求
当请求过多时,每个线程占用一定的内存空间,服务器瘫痪了
JDK1.4开始支持NIO,适用于连接数目多且连接比较短的架构,比如聊天服务器,并发局限于应用中。
一个请求一个线程

三:AIO:异步非阻塞
AIO:一个有效请求一个线程
JDK7开始支持AIO,适用于连接数目多且连接比较长的结构
比如相册服务器,充分调用OS参与并发操作

14.Files的常用方法有哪些?

方法:
exist()
createFile()
createDirectory()
write()
read()
copy()
size()
delete()
move()

15.什么是反射?

所谓反射,是Java在运行时进行自我观察的能力,
通过class(),constructor(),field(),method()四个方法获取一个类的各个组成部分

在Java运行时环境中,对任意一个类,可以知道类有哪些属性和方法。
这种动态获取类的信息以及动态调用对象的方法的功能来自于反射机制。

16.什么是Java序列化?什么情况下需要序列化?

序列化就是一种用来处理对象流的机制。将对象的内容流化,将流化的对象传输于网络之间

序列化是通过实现Serializable接口,该接口没有需要实现的方法,
implements Serializable只是为了标注该对象是可被序列化的,
使用一个输出流(FileOutputStream)来构造一个ObjectOutputStream对象,
接着使用ObjectOutputStream对象的writeObject(Object object)方法就可以将参数object对象写出到磁盘,
需要恢复的时候使用输入流

序列化是将对象转换为容易传输的格式的过程。
例如,可以序列化一个对象,然后通过HTTP通过Internet在客户端和服务器之间传输该对象。
在另一端,反序列化将从流中心构造成对象。

一般程序在运行时,产生对象,这些对象随着程序的停止而消失,但我们想将某些对象保存下来,
这时,我们就可以通过序列化将对象保存到磁盘中,需要使用的时候通过反序列化获取到之前保存的该对象。

对象序列化的最主要目的就是传递和保存对象,保存对象的完整性和可传递性。

比如通过网络传输或者把一个对象保存成本地一个文件的时候,需要使用序列化。

17.为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?

一:为什么要使用克隆?
想对一个对象进行复制,又想保留原有的对象进行接下来的操作,这个时候就需要克隆了

二:如何实现对象克隆?
实现Cloneable接口,重写clone()方法
实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆
BeanUtils,apache,Sprng都提供了bean工具,只是这都是浅克隆。

三:深拷贝和浅拷贝区别是什么?
浅拷贝:仅仅克隆基本数据类型,不克隆引用数据类型。
摄拷贝:既克隆基本数据类型,又克隆引用数据类型

在这里插入图片描述

在这里插入图片描述

18.throw和throws的区别?

一:throw
作用在方法内,表示抛出具体异常,由方法体内的语句处理
代码走到throw的时候一定抛出了异常

二:throws
作用在方法的声明上,表示抛出异常,由调用者来进行异常处理,
代码走到throws的时候可能出现异常,也可能没有出现异常

19.final、finally、finalize有什么区别?

一:final
final可以修饰类,变量,方法
final修饰的类不能被继承,final修饰的变量赋值后不能被重新赋值,final修饰的方法不能被重写

二:finally
finally用于抛异常,finally代码块内语句无论是否发生异常,都会执行finally,常用于一些流的关闭

三:finalize()方法是在垃圾回收时调用
一般情况下不需要我们实现finalize()方法,当对象被回收的时候需要释放一些资源,比如释放socket连接

但是当调用finalize()方法时并不意味着GC会立即回收该对象,所以有可能真正调用的时候,对象又不需要回收了,
然后到了真正要回收的时候,因为之前调用过一次,这次又不调用了,产生问题,所以,不推荐使用finalize()方法

20.try-catch-finally中,如果catch中return了,finally还会执行吗?

//try-catch-finally中,如果catch中return了,finally还会执行吗?

public class Test{
	public static void main(String[] args){
		System.out.println(test());
	}
	private static int test(){
		try{
			int ret = 1/0;
			System.out.prinln("try");
		}catch(Exception e){
			System.out.println(e.getMessage());
			return 1;
		}finally{
			System.out.println("finally");
		}
	}
}

控制台输出:
/by zero
finally
1

结论:顺序是catch =finally =catchreturn

21.常见的异常类有哪些?

1.NullPointerException:空指针异常
2.SQLException: 数据库相关的异常
3.IndexOutOfBoundsException:数组下标越界异常
4.FileNotFoundException:打开文件失败时抛出
5.IOException: 当发生某种IO异常时抛出
6.ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出此异常
7.NoSuchMethodException:无法找到某一方法时,抛出
8.ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常
9.NumberFormatException:当试图将字符串转换成数字时,失败了抛出异常
10.IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数
11.ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数除以零时,抛出此类的一个实例

22.hashCode()是什么方法?有什么作用?

JavaObject类有一个方法:
    public native int hashCode()
	
一:hashCode()方法的作用
hashCode()方法主要配合基于散列的集合一起使用,比如HashSet,HashMap,HashTable

当集合需要添加新的元素时,先调用这个对象的hashCode()方法,得到对应的hashCode值,
实际上hashMap中会有一个table保存已经存进去的对象的hashCode值,
如果table中没有存在该hashCode值,则直接存入,如果有,就调用equals()方法与新元素进行比较,
相同就不存了,不同就存入

二:equals()hashCode()的关系
如果equals()方法为truehashCode()一定相等
如果equals()方法为falsehashCode()不一定不相等

如果hashCode()值相等,equals()不一定相等
如果hashCode()值不相等,equals()一定不相等

三:重写equals()方法时,一定要重写hashCode()方法

四:百度百科

hashCode()方法返回该对象的哈希码值,支持该方法是为哈希表提供一些优点,
例如:java.util.Hashtable提供的哈希表

hashCode的常规协定是:
在Java应用程序执行期间,在同一个对象上多次调用hashCode()方法时,必须一致地返回相同的整数,
前提是对象上equals()比较中所用的信息没有被修改。
从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致

如果根据equals(Object)方法,两个对象是相等的,
那么在两个对象中的每个对象上调用hashCode()方法都必须生成相同的整数结果

以下情况不是必须的:
如果根据equals(java.lang.Object)方法,两个对象不相等,那么在两个对象中的任一对象上调用hashCode()方法必定会生成不同的整数结果。
但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。

实际上,由Object类定义的hashCode()方法确实会针对不同的对象返回不同的整数。
(这一般是通过将该对象的内部地址转换成一个整数来实现的,但是JavaTM编程语言不需要这种实现技巧)equals()方法被重写时,通常有必要重写hashCode()方法,
以维护hashCode()方法的常规协定,该协定声明相等对象必须具有相等的哈希码

五:小白解释
1.hashCode是用来查找的,如果你学过数据结构就应该知道,在查找和排序这一章有例如内存中有这样的位置
0 1 2 3 4 5 6 7
而我有个类,这个类有个字段叫ID,我要把这个类存放在以上8个位置之一,
如果不用hashCode()方法而任意存放,那么当查找时就需要到这个八个位置挨个查找一遍,或者用二分法之类的算法。
但如果用hashCode()那么就会使得效率提高很多。
我们这个类有个字段叫ID,那么我们就定义我们的hashCode值为ID%8,
然后把我们的类存放在取得余数那个位置。比如我们的ID为9,9除以8的余数为1,那么我们就把该类存放在1这个位置,
如果ID为13,求得的余数是5,那么我们就把该类放到5这个位置,
这样,以后在查找该类时就可以通过ID除以8求余数直接找到类存放的位置了。

2.但是如果两个类有相同的hashCode值怎么办呢?(我们假设上面的类的ID字段不是唯一的),
例如9除以817除以8的余数都是1,那么这是不是不合法的呢?回答是:是合法的。
那么如果更准确的判断呢?在这个时候就需要重新定义equals()方法的判断方法了

 也就是说,我们先通过hashCode值来判断两个类是否存放在同一个桶里,但这个桶里可能有很多个类,
那么我们就需要再通过equals()方法来这个桶里找到我们要找到的类

那么,重写了equals()方法,为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,
我们需要先通过hashCode值来确定到底放到哪个桶,然后通过equals()方法来确定具体哪个值

常用的Hash算法有哪些?
1.加法Hash:所谓的加法哈希就是把输入元素一个一个的加起来构成最后的结果
2.位运算Hash:这类型Hash函数通过利用各种位运算(移位或异或)来充分的混合输入元素
3.乘法Hash33*hash+key.charAt(i)

什么是一致性哈希?
设计目标是为了解决因特网中的热点(Hot spot)问题,一致性hash算法提出了在动态变化的Cache环境中,判定哈希算法好坏的四个定义:
1.平衡性(Balance)
2.单调性(Monotonicity)
3.分散性(Spread)
4.负载(Load)

23.Java中操作字符串都有哪些类?它们之间有什么区别?

一:String
String是不可改变的对象,每次对String类型的改变都会生成一个新的对象

二:StringBuidler
线程不安全,效率高,多用于单线程

三:StringBuffer
线程安全,由于加锁的原因,效率不如StringBuilder,多用于多线程

结论:不频繁的字符串操作使用String,操作频繁的情况不建议使用String

执行字符串修改的效率:
StringBuilder > StringBuffer > String

24.Java中有哪些引用类型?

Java中四种引用类型:强引用、软引用、弱引用、虚引用
(1).强引用
Java中默认声明的就是强引用,比如:
Object obj = new Object();
obj = null;

只要强引用存在,垃圾回收器将永远不会回收被引用的对象。
如果想被回收,可以将对象设置为null

(2).软引用(SoftReference)
在内存足够的时候,软引用不会被回收
只有在内存不足时,系统才会回收软引用对象,
如果回收了软引用对象之后仍然没有足够的内存,
才会抛出内存溢出异常。

byte[] buff = new byte[1024*1024]
SoftReference<byte[]> sr = new SoftReference<>(buff);

(3).弱引用(WeakReference)
进行垃圾回收时,弱引用就会被回收

软引用和弱引用的区别是什么?
软引用会尽可能的保留引用直到JVM内存不足时才会被回收
所以软引用非常适合缓存应用
弱引用一旦失去最后一个强引用就被GC回收

(4).虚引用(PhantomReference)

(5).引用队列(ReferenceQueue)

引用队列可以与软引用,弱引用,虚引用一起配合使用
当垃圾回收器准备回收一个对象时,如果发现它还有引用,
就会在回收对象之前,把这个引用加入到引用队列中。

程序可以通过判断引用队列中是否加入了引用,
来判断被引用的对象是否将要被垃圾回收器回收,
这样可以在对象被回收之前采取一些必要的措施。

25.在Java中,为什么不允许从静态方法中访问非静态变量?

1.静态变量属于类本身,在类加载的时候就会分配内存,可以通过类名直接访问
2.非静态变量属于类的对象,只有在类的对象产生时,才会分配内存,通过类的实例去访问
3.静态方法也属于类本身,但是此时没有类的实例,内存中没有非静态变量,所以无法调用

26.说说JavaBean的命名规范


1.JavaBean类必须是一个公共类,将该类访问权限设置为public
2.JavaBean类必须有一个空的构造函数:
类中必须有一个不带参数的公用构造器,
此构造器也应该通过调用各个特性的设置方法来设置特性的缺省值
3.一个JavaBean类不应有公共实例变量,类变量都为private
4.持有值应该通过一组存取方法(getXxx()setXxx())来访问:
对于每个特性,应该有一个带匹配公用getter()setter()方法的专用实例变量。

属性为布尔类型,可以使用isXxx()方法代替getXxx()方法
通常属性名是要和包名,类名,方法名,字段名,常量名作出区别的
首先:必须用英文,不要用汉语拼音

(1).(package)
用于将完成不同功能的类分门别类,放在不同的目录()下,
包的命名规则:将公司域名反转作为包名。比如www.sohu.com
对于包名:每个字母都需要小写。
比如:com.sohu.test该包下的Test类的全名是:com.sohu.test.Test.java

如果定义类的时候没有使用package,
那么Java就认为我们所定义的类位于默认包里面(default package)

(2).类
首字母大写,如果一个类由多个单词构成,
那么每个单词的首字母都大写,而且中间不使用任何的连接符。
尽量使用英文,如ConnectionFactory

(3).方法
首字母全部小写,如果一个方法由多个单词构成,
那么从第二个单词开始首字母大写,不使用连接符,如addPerson

(4).字段
与方法相同。如ageOfPerson

(5).常量
所有单词的所有字母都是大写,如果有多个单词,那么使用下划线连接即可
如:public static final int AGE_OF_PERSON = 20;
常量通常用staticfinal修饰

27.JavaBean属性命名规范问题分析?

1.JavaBean属性命名尽量使用常规的驼峰式命名规则
2.属性名第一个单词尽量避免使用一个字母:如eBook,eMail
3.boolean属性名避免使用“is”开头的名称
4.随着JDK,eclipse,spring等软件版本的不断提高,
低版本出现的问题可能在高版本中解决了
低版本原来正常的代码可能在高版本环境下不再支持

28.什么是Java的内存模型?

为什么要提出Java内存模型?

并发编程三大问题
1.CPU缓存,在多核CPU的情况下,带来了可见性问题
2.操作系统对当前执行线程的切换,带来了原子性问题
3.编译器指令重排优化,带来了有序性问题

为了解决并发编程的三大问题,
提出了JSR-133,新的Java内存模型,JDK5开始使用

简单总结下
Java内存模型是JVM的一种规范
定义了共享内存在多线程程序中读写操作行为的规范
屏蔽了各种硬件和操作系统的访问差异,
保证了Java程序在各种平台下对内存的访问效果一致

解决并发问题采用的方式:
1.限制处理器优化和使用内存屏障
2.增强了三个同步原语(synchronizedvolatilefinal)的内存语义
3. 定义了happens-before规则

29.举例说明什么情况下会更倾向于使用抽象类而不是接口?

接口和抽象类都遵循“面向接口而不是实现编码”设计原则,
它可以增加代码的灵活性,可以适应不断变化的需求。
下面有几个点可以帮助你回答这个问题:
在Java中,你只能继承一个类,但可以实现多个接口。
所以一旦你继承一个类,你就失去了继承其他类的机会了。

接口通常被用来表示附属描述或行为
如:RunnableCloneableSerializable等等
因此当你使用抽象类来表示行为时,
你的类就不能同时是RunnableCloneable,Serializable
(注:这里的意思是指如果把Runnable等实现为抽象类的情况),
因为在Java中你不能继承两个类,
但当你使用接口时,你的类就可以同时拥有多个不同的行为或者是附属多种状态

1.在一些对时间要求比较高的应用中,倾向于使用抽象类,它会比接口稍快一些。
2.如果希望把一系列行为都规范在类继承层次内,并且可以更好地在同一个地方进行编码,那么抽象类是一个更好的选择。
3.有时,接口和抽象类可以一起使用,接口中定义函数,而在抽象类中定义默认的实现

30.实例化对象有哪几种方式?

(1).new创建对象

(2).clone()

(3).通过反射机制创建,先获得字节码对象,再调用对象的newInstance()方法
    Class<?> cls = Class.forName("com.dao.User");
	User u = (User)cls.newInstance();

(4).序列化反序列化
序列化
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:/data.txt"));
out.writeObject(user1);
out.close();

反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:/data.txt"));
User user2 = (User)in.readObject();
System.out.println("反序列化user: "+user2);
in.close();

31.byte类型127+1等于多少?

byte的范围是-128~127

字节长度为8位,最左边的是符号位
而127的二进制为01111111,
所以执行+1操作时,01111111变为10000000

计算机中负数是以补码存储的,左边第一位为符号位。

那么负数的补码转换成十进制如下:
一个数如果为正,则它的原码,反码,补码相同
一个正数的补码,将其转化为十进制,可以直接转换

已知一个负数的补码,将其转换为十进制数,步骤如下:
1.先对各位取反
2.将其转换为十进制数
3.加上负号,再减去1

例如10000000,最高位是1,是负数
1.对各位取反得01111111
2.转换为十进制数就是127
3.加上负号,再减去1-128

32.Java容器都有哪些?

.Collection
1.list
ArrayList,LinkedList,Vector

2.Set
HashSet,TreeSet.Map
HashMap,Hashtable,TreeMap

33.Collection和Collections有什么区别?

(1).Collection是最基本的集合接口,
Collection派生了两个子接口list和set,分别定义了两种不同的存储方式

(2).Collections是一个包装类,它包含各种有关集合操作的静态方法
(对集合的搜索,排序,线程安全化等)
此类不能实例化,就像一个工具类,服务于Collection框架

34.List和Set区别?

.List简介
两种ListArrayList,优点在于随机访问元素
LinkedList,优点是快速的插入或删除

ArrayList:底层由数组实现的List,优点是随机访问快
缺点是插入与移除元素的速度很慢
LinkedList:对顺序访问进行了优化,插入与删除的开销较小。
缺点是随机访问慢
 
LinkedList还具有下列方法:addFirst()addLast()getFirst()getLast(),
removeFirst()removeLast(),这些方法(没有在任何接口或基类中定义过)
使得LinkedList可以当做堆栈,队列和双向队列使用

二.Set简介
Set具有与Collection完全一样的接口,因此没有任何额外的功能。
实际上Set就是Collection,只是行为不同。
这是继承与多态思想的典型应用:表现不同的行为。
Set不保存重复的元素

Set:存入Set的每个元素都必须是唯一的,因为Set不保存重复元素。加入Set的元素必须
定义equals()方法以确保对象的唯一性。SetCollection有完全一样的接口。
Set接口不保证维护元素的次序。
HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()TreeSet: 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。

三.List与Set的区别
1.元素是否有放入顺序,元素是否可以重复
List特点:元素有放入顺序,元素可重复,
Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,
(Set元素虽然无放入顺序,但是元素在Set中的位置是由该元素的HashCode值决定的,
其位置是固定的,加入SetObject必须定义equals()方法)
2.是否支持for循环
另外List支持for循环,也就是通过下标来遍历,也可以用迭代器,
但是Set只能用迭代器,因为他无序,无法用下标来取得想要的值。


四.Set和List对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变
List:和数组类型,List可以动态增长,查找元素效率高,
插入删除元素效率低,因为会引起其他元素位置改变。

35.HashMap和Hashtable有什么区别?

1.是否线程安全
HashMap是线程不安全的,Hashtable是线程安全的

2.是否允许key和value为null
HashMap中允许键和值为nullHashTable不允许

3.默认容器大小
HashMap的默认容器是16  ,为2倍扩容
HashTable的默认容器是11,为2+1扩容

36.说一下HashMap的实现原理?

一:简介
HashMap基于Map接口,元素以键值对方式存储,允许有null值,
HashMap是线程不安全的

二:基本属性
初始化大小,HashMap默认是16, 2倍扩容
负载因子是 0.75
初始化的默认数组
size
threshold,判断是否需要调整HashMap的容量

三:HashMap的存储结构
JDK7中采用数组+链表的存储形式
HashMap采取Entry数组来存储key-value,每一个键值对组成了一个Entry实体,
Entry类实际上是一个单向的链表结构,它具有next指针,指向下一个Entry实体,
以此来解决Hash冲突的问题。
HashMap实现一个内部类Entry,重要的属性有hash,key,value,next

JDK8中采用数组+链表+红黑树的存储形式。
HashMap最多只允许一条记录的键为null,允许多条记录的值为nullHashMap非线程安全
ConcurrentHashMap线程安全。
解决碰撞:
当出现哈希冲突时,运用拉链法,将关键字为同义词的结点链接在一个单链表中,
散列表长m,则定义一个由m个头指针组成的指针数组T,地址为i的结点插入以T(i)为头指针的单链表中。
当链表长度超过阈值8时,将链表转换为红黑树,在性能上进一步得到提高。

37.Set有哪些实现类?

(1).HashSet
HashSetSet接口的实现类,
Set下面最主要的实现类就是HashSet(也就是用的最多的)
此外还有LinkedHashSetTreeSet
HashSet是无序的,不可重复的。
通过对象的HashCode()equals()方法保证对象的唯一性。
HashSet内部的存储结构是哈希表,是线程不安全的。

(2).TreeSet
TreeSet对元素进行排序的方式:
元素自身具备比较功能的,需要实现Comparable接口,并覆盖CompareTo()方法
元素自身不具备比较功能的,需要实现Comparator接口,并覆盖compare()方法

(3).LinkedHashSet
LinkedHashSet是一种有序的Set集合,即其元素的存入和输出的顺序是相同的。

38.说一下HashSet的实现原理

HashSet实际上是一个HashMap实例,数据存储结构都是数组+链表
HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,
而value都是一个统一的对象PRESENT 

private static final Object PRESENT = new Object();

HashSetadd()方法调用的是底层HashMap中的put()方法,
put()方法要先判断插入值是否存在,
所以HashSetadd()方法,
首先判断元素是否存在
如果存在则不插入
如果不存在则插入
这样就保证了HashSet中不存在重复值

通过对象的hashCode()方法和equals()方法保证对象的唯一性

39.ArrayList和LinkedList的区别是什么?

ArrayList底层是动态数组,查找和遍历元素效率高
LinkedList底层是双向链表,增加和删除元素效率高

40.如何实现数组和List集合之间的转换?

public class Test{
	public static void main(String[] arts){
		String[] arr = {"zs","ls","ww"};
		//Arrays工具类的asList()将数组转换为List
		List<String> list = Arrays.asList(arr);
		System.out.println(list);
		
		ArrayList<String> list1 = new ArrayList<String>();
		list1.add("张三");
		list1.add("李四");
		list1.add("王五");
		//List的toArray()将List转换为数组
		String[] arr1 = list1.toArray(new String[list1.size()]);
		System.out.println(arr1);
		for(int i = 0; i < arr1.length; i++){
			System.out.println(arr1[i]);
		}
	}
}

41.在Queue中poll()和remove()有什么区别?

主要区别是当元素不存在时,对元素进行操作是抛出异常还是返回null

一:offer()方法和add()方法区别:
增加新项时,如果队列已经满了,
使用offer()方法会返回false
使用add()方法会抛出异常

二:poll()方法和remove()方法区别:
poll()方法和remove()方法都是从队列中删除第一个元素,
当队列第一个元素不存在时,
使用poll()方法返回null
使用remove()抛出异常

三:peek()方法和element()方法:
peek()方法和element()方法都是用于查询队列头部元素
当队列头部元素为空时
使用peek()方法返回null
使用element()方法抛出异常

42.哪些集合类是线程安全的?

一:Vector:就比ArrayList多了个同步化机制(线程安全)

二:Stack:栈,也是线程安全的,继承于Vector

三:HashTable:就比HashMap多了个线程安全机制。

四:ConcurrentHashMap:是一种高效但是线程安全的集合。

43.迭代器Iterator是什么?

Iterator对象专门提供了一些方法用于处理集合中的元素,
例如删除和获取集合中的元素,
该对象就叫做迭代器(Iterator)。

44.Iterator怎么使用?有什么特点?

Iterator接口源码中的方法:
1.java.lang.Iterator接口被java.util.Collection接口继承,
java.util.Collection接口的iterator()方法返回一个Iterator对象

2.hasNext()检查集合中是否还有元素

3.next()方法获得集合中的下一个元素

4.remove()方法将迭代器新返回的元素删除

45.Iterator和ListIterator有什么区别?

一:ListIterator继承自Iterator

二:ListIteratorIterator多很多方法
1.add(E e) 将指定元素插入列表,插入位置为迭代器当前位置之前
2.set(E e) 将迭代器返回的最后一个元素替换参数e
3.hasPrevious() 迭代器当前位置,反向遍历集合判断是否含有元素
4.previous() 迭代器当前位置,反向遍历集合,即获取上一个元素
5.previousIndex() 迭代器当前位置,反向遍历集合,即返回上 一个元素的下标
6.nextIndex() 迭代器当前位置,返回下一个元素的下标

三:使用范围不同,
Iterator可以迭代所有集合
ListIterator只能用于遍历List集合及其子类
1.ListIterator有add()方法,可以向List中添加对象,而Iterator不能
2.ListIterator有hasPrevious()previous()方法,可以实现逆向遍历,而Iterator不能
3.ListIterator有nextIndex()previousIndex()方法,可定位当前索引的位置,而Iterator不能
4.ListIterator有set()方法,可以实现对List的修改,而Iterator仅能遍历,不能修改

46.怎么确保一个集合不能被修改?

我们很容易想到用final关键字进行修改

final关键字可以修饰类,方法,成员变量,
final修饰的类不能被继承,
final修饰的方法不能被重写,
final修饰的成员变量必须初始化值
如果这个变量是基本数据类型,表示这个变量的值是不可改变的
如果说这个成员变量是引用类型,则表示这个引用的地址值是不能改变的,
但是这个引用所指向的内容还是可以修改的。

我们可以做一个实验:
可以看到,我们用final关键字定义了一个Map集合,这时候我们往集合里面传值
第一个键值对是(1,1),然后我们进行修改,可以把key为1的值改为100,
说明我们是可以修改Map集合的值的

那我们应该怎么做才能确保集合不被修改呢?
我们可以采用Collection包下的unmodifiableMap(Map)方法,
通过这个方法返回的Map是不可被修改的,如果修改会报java.lang.UnsupportedOperationException异常
同理:Collection包也提供了对ListSet集合的方法
Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)

public class Test{
	public static void main(String[] args){
		Map<String,String> hashMap = new HashMap<String,String>();
		hashMap.put("1","zhangsan");
		hashMap.put("2","lisi");
		hashMap.put("1","wangwu");
		
	    //怎么做才能确保集合不被修改呢?可以采用Collection包下的unmodifiableMap(Map)方法
		Map<String,String> hashMap1 = Collections.unmodifiableMap(hashMap);
		hashMap1.put("3","zs");
		System.out.println("unmodifiableMap,"+hashMap1);
	}
}

47.队列和栈是什么?有什么区别?

一:进出顺序不同
队列先进先出FIFO,栈先进后出FILO

二:遍历数据速度不同,是否需要开辟临时空间
栈只能从头部取数据,也就是最先放入的需要遍历整个栈最后才能取出来,栈遍历可能比较慢
而且栈在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性。

队列则不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,队列遍历速度快
但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影响数据结构,速度要快的多

48.Java8开始ConcurrentHashMap为什么舍弃分段锁?

ConcurrentHashMap的原理是引用了内部的Segment(ReentranLock)分段锁,
保证在操作不同段Map的时候,可以并发执行,
操作同段Map的时候,进行锁的竞争和等待。
从而达到线程安全,且效率大于synchronized

但是在Java8之后,JDK却弃用了这个策略,重新使用了synchronized+CAS  

弃用原因:
通过JDK的源码和官方文档看来,他们认为的弃用分段锁的原因有以下几点:
加入多个分段锁浪费内存空间
生产环境中,Map在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待

为了提高GC的效率,新的同步方案是:
源码保留了segment代码,但并没有使用

49.ConcurrentHashMap(JDK8)为什么要使用synchronized而不是如ReentranLock这样的可重入锁?

一:锁的粒度
首先锁的粒度并没有变粗,甚至变得更细了。
每当扩容一次,ConcurrentHashMap的并发度就扩大一倍

二:Hash冲突
JDK7中,ConcurrentHashMap从过二次hash的方式(Segment->HashEntry)能够快速的查找的元素。
在JDK8中通过数组+链表+红黑树的形式弥补了put()get()时的性能差距
JDK8中,在ConcurrentHashMap进行扩容时,
其他线程可以通过检测数组中的节点决定是否对这条链表(红黑树)进行扩容,
减小了扩容的粒度,提高了扩容的效率

为什么是synchronized,而不是ReentranLock呢?

一:减少内存开销
假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。
但并不是每个节点都需要获得同步支持的,
只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费


二:获得JVM的支持
可重入锁毕竟是API这个级别的,后续的性能优化空间很小
synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。
这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能的提升。

50.ConcurrentHashMap和HashTable有什么区别?

ConcurrentHashMap融合了HashMapHashTable的优势
HashMap是不同步的,但是单线程情况下效率高
HashTable是同步情况下保证程序执行的正确性

ConcurrentHashMap锁的方式是细粒度的。
ConcurrentHashMapHash分为16个桶(默认值),
诸如get()put()remove()等常用操作只锁住当前需要用到的桶

ConcurrentHashMap的读取并发,因为读取的大多数时候都没有锁定,
所以读取操作几乎是完全的并发操作,只是在求size()时才需要锁定整个Hash

而且在迭代时,ConcurrentHashMap使用了不同于传统集合的快速失败迭代器的另一种迭代方式,即弱一致迭代器。
在弱一致迭代器这种方式中,当iterator被创建后集合再发生改变就不会抛出ConcurrentModificationException,
取而代之的是在改变时new新的数据而不是影响原来的数据,
iterator完成后再将头指针替代为新的数据,这样iterator时使用的是原来的数据

51.HashMap和HashSet的区别是什么?

一:先了解HashCode
Collection接口有两子类:1.List  2.Set
List:元素有序,可以重复
Set:元素无需,不可重复

要想保证元素的不重复,拿什么来判断呢?
这就需求Object.equals()方法了,但是如果元素有很多,增加一个元素就一定要判断n次吗?显然这样不现实。

于是Java采用了哈希表的原理。
哈希算法也称为散列算法,是将数据依特定算法直接指定到一根地址上,
初学者可以简单的理解为,hashCode()方法返回的就是对象存储的物理位置(实际上并不是)。

这样一来,当集合添加新的元素时,
先调用这个元素的hashCode()方法,就一下定位到它应该放置的物理位置上。
如果这个位置上没有元素,就可以直接存储在这个位置上
如果这个位置上有元素,就调用它的equals()方法与新元素进行比较,
相同的话就不存了,不相同的话就散列到其它位置
好处是这样一来调用equals()方法的次数就大大降低了

HashCode()方法的好处是:
总结:在集合元素查找时,hashCode()方法的存在能大大降低对象equals()方法比较次数,提高查找效率

Java对于eqauls()方法和hashCode()方法是这样规定的:
1.equals()相等的两个对象,HashCode值一定要相同
equals()方法不相等的两个对象,Hashcode()有可能相等(由于哈希码生成的随机性造成,发生哈希冲突)。
2.HashCode值相同,两个对象不一定相同,equals()有可能相等,也可能不等
Hashcode()不等,一定能推出equals()也不等

什么是哈希冲突?
如果两个Java对象不相等,但是他们的哈希码相等,都存入HashMap时会发生哈希冲突,
也就是将对象存放在HashMap内部数组的位置索引相同,
这时HashMap会在该位置建立一个链接表,将AB串起来放在该位置,
显然,该情况不违反HashMap的使用规则,是允许的。
当然,哈希冲突越少越好,尽量采用好的哈希算法避免哈希冲突


二:HashMapHashSet的区别是什么?
1.实现接口
HashMap实现Map接口
HashSet实现Set接口

2.存储内容
HashMap存储键值对
HashSet存储对象

3.添加元素的方法
HashMap使用put()方法添加元素
HashSet使用add()方法添加元素

4.使用什么计算hashCode值
HashMap使用key计算hashCode值
HashSet使用成员对象计算hashCode值,对于两个对象来说,hashCode值可能相同,所以继续用equals()方法判断对象内容是否相同,
如果两个对象不同则返回false

5.运行速度
HashMapHashSet

52.请谈谈ReadWriteLock和StampedLock

ReadWriteLock包括两种子锁
1.ReadWriteLock是JDK5 中提供的读写分离锁
ReadWriteLock可以实现多个读锁同时进行,但是读与写和写与写互斥,只能有一个写锁线程在进行
读-读不互斥:读读之间不阻塞。
读-写互斥:读阻塞写,写也会阻塞读
写-写互斥:写写阻塞
2.StampedLock
StampedLock是JDK8提供的一种读写锁,相比较与ReentrantReadWriteLock性能更好
因为ReenTrantReadWrtieLock在读写之间是互斥的,使用的是一种悲观策略,
ReenTrantReadWrtieLock缺点是:在读线程特别多的情况下,会造成写线程处于饥饿状态,
虽然可以在初始化的时候设置为true指定为公平,但是吞吐量又下去了
而StampedLock是提供了一种乐观策略,
优点是更好的实现读写分离,并且吞吐量不会下降

StampedLock包括三种子锁
1.写锁WriteLock
WriteLock是一个独占锁写锁
当一个线程获得该锁后,其他请求读锁或者写锁的线程阻塞,
获得成功后,会返回一个stamp(凭据)变量来表示该锁的版本,
在释放锁时调用unlockWrite()方法传递stamp参数,提供了非阻塞式获取锁tryWriteLock
2.悲观读锁readLock
readLock是一个共享读锁,在没有线程获取写锁情况下,多个线程可以获取该锁。
如果有写锁获取,那么其他线程请求读锁会被阻塞。
悲观读锁会认为其他编程可能要对自己操作的数据进行修改,所以需要先对数据进行加锁,
这是在读少写多的情况下考虑的。
请求该锁成功后悔返回一个stamp值,
在释放锁时调用unlockRead()方法传递stamp参数,提供了非阻塞式获取锁方法tryReadLock 
3.乐观读锁tryOptimisticRead
tryOptimisticRead相对比悲观读锁,在操作数据前并没有通过CAS设置锁的状态,
如果没有线程获取写锁,则返回一个非0的stamp变量,
获取该stamp后在操作数据前还需要调用validate()方法来判断期间是否有线程获取了写锁,
如果是返回值为0则有线程获取写锁,
如果不是0则可以使用stamp变量的锁来操作数据。
由于tryOptimisticRead并没有修改锁状态,所以不需要释放锁。
这是读多写少的情况下考虑的,不涉及CAS操作,所以效率较高
在保证数据一致性上需要复制一份要操作的变量到方法栈中,
并且在操作数据时可能其他写线程已经修改了数据,
而我们操作的是方法栈里面的数据,也就是一个快照,
所以最多返回的不是最新的数据,但是一致性得到了保证。

53.线程的run()和start()有什么区别?

每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,
run()方法称为线程体。
通过调用Thread类的start()方法来启动一个线程。

run()方法用于执行线程的运行时代码
start()方法只用于启动线程

run()可以重复调用
start()只能调用一次

start()方法来启动一个线程,真正实现了多线程运行。
调用start()方法无需等待run()方法体代码执行完毕,
可以直接继续执行其他的代码。
此时线程是出于就绪状态,并没有运行。
然后通过此Thread类调用方法start()来完成其运行状态,
run()方法运行结束,此线程终止,然后CPU再调度其它线程。

run()方法是在本线程里的,只是线程里的一个方法,而不是多线程的。
如果直接调用run()方法其实就相当于调用了一个普通的方法而已,
直接调用run()方法必须等待run()方法执行完毕才能执行下面的代码,
所以执行路径还是只有一条,根本就没有多线程的特征,
所以在多线程执行时要使用start()方法而不是run()方法

54.为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

new一个Thread类,线程进入了新建状态
调用Thread类的start()方法,会启动一个线程并使线程进入了就绪状态
当分配到时间片后就可以开始运行了
start()会执行线程的相应准备工作,然后自动执行run()方法里的代码,这是真正的多线程工作

而直接执行run()方法,会把run()方法当成一个main线程下的普通方法去执行
并不会在新的线程中执行它,所以这并不是多线程工作

总结:调用start()方法方可启动线程并使线程进入就绪状态,
而run()方法只是thread的一个普通方法调用,还是在主线程里执行

55.Synchronized用过吗?其原理是什么?

.可重入性
synchronized的锁对象中有一个计数器(recursions变量)会记录线程获得几次锁
可重入的好处
可以避免死锁
可以让我们更好的封装代码
synchronized是可重入锁,每个锁对象会有一个计数器记录线程获取几次锁,在执行完同步代码块时,计数器的数量会-1,直到计数器的数量为0,就释放这个锁。

二.不可中断性
一个线程获得锁后,另一个线程想要获得锁,必须处于阻塞或等待状态,
如果第一个线程不放锁,第二个线程会一直阻塞或等待,不可被中断
synchronized属于不可被中断
Lock lock()方法是不可中断的
Lock tryLock()方法是可中断的

56.JVM对Java的原生锁做了哪些优化?

(1).自旋锁
在线程进行阻塞的时候,先让线程自旋等待一段时间,
可能这段时间其它线程已经解锁,
这时就无需让线程再进行阻塞操作了
自旋默认次数是10(2).自适应自旋锁
自旋锁的升级,自旋的次数不再固定,由前一次自旋次数和锁的拥有者的状态决定

(3).锁消除
在动态编译同步代码块的时候,
JIT编译器借助逃逸分析技术来判断锁对象是否只被一个线程访问,
而没有其他线程,这时就可以取消锁了

(4).锁粗化
当JIT编译器发现一系列的操作都对同一个对象反复加锁解锁,
甚至加锁操作出现在循环中,此时会将加锁同步的范围粗化到整个操作系列的外部

锁粒度:不要锁住一些无关的代码
锁粗化:可以一次性执行完的,不要多次加锁执行

57.为什么wait(),notify()和notifyAll()在Object类中?

Java中,任何对象都可以作为锁,并且wait()notify()等方法用于等待对象的锁或者唤醒线程,
在Java的线程中并没有可供任何对象使用的锁,
所以想让任意对象都能调用,方法一定要定义在Object类中

有人会说,既然线程放弃对象锁,那也可以把wait()定义在Thread类中啊,
新定义的线程继承于Thread类,也不需要重新定义wait()方法的实现。
然而这样做有一个非常大的问题,一个线程完全可以持有很多锁,
当你一个线程要放弃锁的时候,到底要放弃哪个锁?
当然了,这种设计并不是不能实现,只是管理起来更加复杂

综上所述,wait()notify()notifyAll()方法要定义在Object类中

58.Java如何实现多线程之间的通讯和协作?

可以通过中断和共享变量的方式实现线程间的通讯和协作

比如说最经典的生产者-消费者模型:
当队列满时,生产者需要等待队列有空间才能继续往里面放入商品
而在等待的期间内,生产者必须释放对临界资源(及队列)的占有权
因为生产者如果不释放对临界资源的占用权,
那么消费者就无法消费队列中的商品,就不会让队列有空间
那么生产者就会一直无限等待下去。
因此。一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。
然后等待消费者消费了商品,然后消费者通过生产者队列有空间了。
同样地,当队列空时,消费者也必须等待,
等待生产者通知它队列中有商品了。
这种互相通信的过程就是线程间的协作。

Java中线程通信协作的最常用的两种方式:
1.synchronized加锁的线程的Object类的wait()notify()notifyAll()
2.ReentrantLock类加锁的线程的Condition类的await()signal()signalAll()

线程间直接的数据交换
通过管道进行线程间通信:1.字节流   2.字符流

59.Thread类中的yield()方法有什么作用?

yield()应该做的是让当前运行线程回到可运行状态,
以允许具有相同优先级的其他线程获得运行机会。
因此yield()的目的是让相同优先级的线程之间能适当的轮转执行,
但是,实际中无法保证yield()达到让步目的,
因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待或睡眠或阻塞状态
在大多数情况下,yield()将导致线程从运行状态转到可运行状态,
但有可能没有效果,因为让步的线程还有可能被线程调度程序再次选中

60.为什么说Synchronized是非公平锁?

当锁被释放后,任何一个线程都有机会竞争得到锁
这样做的目的是提高效率
但缺点是可能产生线程饥饿现象

什么是线程饥饿?
饥饿指的是线程因无法访问所需资源而无法执行下去的情况:
1.在CPU繁忙时,如果一个线程优先级太低,就有可能遇到一直得不到执行
持有锁的线程,如果执行的时间过长,会导致其他阻塞的线程一直获取不到锁
2、线程饥饿的解决方案

有三种方案:
1.保证资源充足
2.公平地分配资源,如果有需求可以使用公平锁,不过效率较低,很少使用。
3.避免持有锁的线程长时间执行

61.请谈谈volatile有什么特点,为什么它能保证变量对所有线程的可见性?

volatile只能作用于变量,保证了操作可见性和有序性,不保证原子性
可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值

在Java的内存模型中分为主内存和工作内存,
Java内存模型规定所有的变量存储在主内存中,
每条线程都有自己的工作内存

主内存和工作内存之间的交互分为8个原子性操作
1.lock()
2.unlock()
3.read()
4.load()
5.assign()
6.use()
7.store()
8.write()

volatile修饰的变量,只有对volatile进行assign操作,
才可以load,只有load才可以use,
这样就保证了在工作内存操作volatile变量,都会同步到主内存中

62.为什么说Synchronized是一个悲观锁?乐观锁的实现原理又是什么?什么是CAS?它有什么特性?

Synchronized的并发策略是悲观的,
不管是否产生竞争,任何数据的操作都必须加锁

乐观锁的核心是CAS,
CAS包括内存值,预期值,新值
只有当内存值等于预期值时,才会将内存值修改为新值
CAS是“Compare and Swap”的缩写,中文名为“比较并交换”。它是一种多线程同步机制,常用于实现线程安全的计数器、缓存、队列等数据结构。CAS操作包括三个参数:内存地址V、当前预期值A、新值B。当且仅当预期值A和内存地址V中的值相同时,才将内存地址V中的值更新为新值B。CAS操作是原子性的,保证了多线程情况下的数据一致性和线程安全。

什么是CAS操作的ABA问题:
CAS操作的ABA问题指的是在一些特定情况下,虽然CAS操作仍然成功执行,但是实际上CAS操作成功的条件已经不满足了。具体来说,如果一个线程在执行CAS操作时,发现内存地址V中的值与预期值A相同,于是就将内存地址V中的值更新为新值B。但是,在这个操作过程中,另外一个线程也访问了内存地址V,将它的值从B改为了C,然后又将它的值从C改回了B,此时CAS操作仍然成功,因为内存地址V的值仍然是B。这就是ABA问题。

为了解决ABA问题,可以使用版本号机制。每次对内存地址V进行更新时,不仅要更新其值,还要将版本号加1。这样,在执行CAS操作时,只有当版本号与预期值A都相同时,才进行更新操作。如果此时内存地址V的值已经被修改过了,那么版本号就不会匹配,CAS操作就会失败。这样就避免了ABA问题的发生。

63.乐观锁一定就是好的吗?

乐观锁认为对一个对象的操作不会引发冲突
所以每次操作都不进行加锁,只是在最后提交更改时验证是否发生冲突,
如果冲突则再试一遍,直至成功为止,这个尝试的过程称为自旋

乐观锁没有加锁
但乐观锁引入了ABA问题,此时一般采用版本号进行控制
也可能产生自旋次数过多的问题
此时并不能提高效率,反而不如直接加锁的效率高
只能保证一个对象的原子性,可以封装成对象,再进行CAS操作 

64.请尽可能详尽地对比Synchronized和ReentrantLock的异同

一:相似点
它们都是阻塞式的同步,
也就是说一个线程获得了对象锁,进入代码块,其它访问该同步块的线程都必须阻塞在同步代码块外面等待,
而进行线程阻塞和唤醒的代码是比较高的。

二:功能区别
SynchronizedJava语言的关键字,是原生语法层面的互斥,需要JVM实现
ReentrantLock是JDK5之后提供的API层面的互斥锁,需要Lock()unlock()方法配合try/finally代码块来完成
Synchronized使用较ReentrantLock便利一些
锁的细粒度和灵活性:ReentrantLock强于Synchronized

三:性能区别
Synchronized引入偏向锁,自旋锁之后,两者的性能差不多,
在这种情况下,官方建议使用Synchronized
1.Synchronized
Synchronized会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令
在执行monitorenter指令时,首先要尝试获取对象锁。
如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计数器+1,
相应的执行monitorexit时,计数器-1,当计算器为0时,锁就会释放。
如果获取锁失败,当前线程就要阻塞,直到对象锁被另一个线程释放为止。

2.ReentrantLock
ReentrantLock是java.util.concurrent包下提供的一套互斥锁,
相比SynchronizedReentrantLock类提供了一些高级功能,主要有如下三项:
1.等待可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,
这相当于Synchronized避免出现死锁的情况,通过lock.lockInterruptibly()来实现这一机制;
2.公平锁,多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁是非公平锁;ReentrantLock默认也是非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好;
3.锁绑定多个条件,一个ReentrantLock对象可以同时绑定多个对象。
ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,
而不是像Synchronized要么随机唤醒一个线程,要么唤醒全部线程。

## 65.ReentrantLock是如何实现可重入性的?
一:什么是可重入性?

一个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;
而这个线程尝试获取自己持有锁时,如果成功说明该锁是可重入的,反之则不可重入。

二:synchronized是如何实现可重入性

synchronized关键字经过编译后,会在同步块的前后分别形成monitorenter和monitorexit两个字节码指令。
每个锁对象内部维护一个计数器,该计数器初始值为0,表示任何线程都可以获取该锁并执行相应的方法。
根据虚拟机规范要求,在执行monitorenter指令时,首先要尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了对象的锁,把锁的计数器+1,
相应的在执行monitorexit指令后锁计数器-1,当计数器为0时,锁就被释放。
如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

三:ReentrantLock如何实现可重入性

ReentrantLock使用内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(非公平锁)和FairSync(公平锁)。Sync通过继承AQS实现,在AQS中维护了一个private volatile int state来计算重入次数,避免频繁的持有释放操作带来的线程问题。

(4ReentrantLock代码实例

// Sync继承于AQS
abstract static class Sync extends AbstractQueuedSynchronizer {
  ...
}
// ReentrantLock默认是非公平锁
public ReentrantLock() {
        sync = new NonfairSync();
 }
// 可以通过向构造方法中传true来实现公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
protected final boolean tryAcquire(int acquires) {
        // 当前想要获取锁的线程
        final Thread current = Thread.currentThread();
        // 当前锁的状态
        int c = getState();
        // state == 0 此时此刻没有线程持有锁
        if (c == 0) {
            // 虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到,
            // 看看有没有别人在队列中等了半天了
            if (!hasQueuedPredecessors() &&
                // 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,
                // 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了 =_=
                // 因为刚刚还没人的,我判断过了
                compareAndSetState(0, acquires)) {
 
                // 到这里就是获取到锁了,标记一下,告诉大家,现在是我占用了锁
                setExclusiveOwnerThread(current);
                return true;
            }
        }
          // 会进入这个else if分支,说明是重入了,需要操作:state=state+1
        // 这里不存在并发问题
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        // 如果到这里,说明前面的if和else if都没有返回true,说明没有获取到锁
        return false;
    }5)代码分析

当一个线程在获取锁过程中,先判断state的值是否为0,如果是表示没有线程持有锁,就可以尝试获取锁。
当state的值不为0时,表示锁已经被一个线程占用了,这时会做一个判断current==getExclusiveOwnerThread(),这个方法返回的是当前持有锁的线程,这个判断是看当前持有锁的线程是不是自己,如果是自己,那么将state的值+1,表示重入返回即可。

66. 什么是锁消除和锁粗化?

一:锁消除
锁消除就是虚拟机根据一个对象是否真正存在同步情况
若不存在同步情况,则该对象的访问无需经过加锁解锁的操作

比如StringBufferappend()方法,因为append()方法需要判断对象是否被占用,
而如果代码不存在锁竞争,那么这部分的性能消耗是无意义的。
于是虚拟机在即时编译的时候就会将上面的代码进行优化,也就是锁消除

    @Override
    public synchronized StringBuffer append(String str){
        toStringCache = null;
        super.append(str);
        return this;
    }

从源码可以看出,append()方法用了synchronized关键字,它是线程安全的。
但我们可能仅在线程内部把StringBuffer当做局部变量使用;
StringBuffer仅在方法内作用域有效,不存在线程安全的问题,
这时我们可以通过编译器将其优化,将锁消除,
前提是Java必须运行在server模式,同时必须开启逃逸分析

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
其中+DoEscapeAnalysis表示开启逃逸分析,+EliminateLocks表示锁消除

    public static String createStringBuffer(String,str1,String str2){
        StringBuffer sBuf = new StringBuffer();
        sBuf.append(str1);//append()方法是同步操作
        sBuf.append(str2);
        return sBuf.toString();
    }
 
逃逸分析:比如上面的代码,它要看sBuf是否可能逃出它的作用域?
如果将sBuf作为方法的返回值进行返回,
那么它在方法外部可能当作一个全局变量使用,就有可能发生线程安全问题
这时就可以说sBuf这个对象发生逃逸了,因而不应将append()操作的锁消除,
但我们上面的代码没有发生锁逃逸,锁消除就可以带来一定的性能提升。

二:锁粗化
锁的请求、同步、释放都会消耗一定的系统资源,
如果高频的锁请求反而不利于系统性能的优化
锁粗化就是把多次的锁请求合并成一个请求,扩大锁的范围,
降低锁请求、同步、释放带来的性能消耗

67 .跟Synchronized相比,可重入锁ReentrantLock其实现原理有什么不同?

1.都是可重入锁

2.ReentrantLock内部是实现了SyncSync继承于AQS抽象类。
Sync有两个实现,一个是公平锁,一个是非公平锁,通过构造函数定义。
AQS中维护了一个state来计算重入次数,避免频繁的持有释放操作带来的线程问题。

3.ReentrantLock只能定义代码块
而Synchronized可以定义方法和代码块。

4.Synchronized是JVM的一个内部关键字,
而ReentrantLock是JDK5之后引入的一个API层面的互斥锁

5.Synchronized实现自动的加锁、释放锁,
而ReentrantLock需要手动加锁和释放锁,中间可以暂停

6.Synchronized由于引入了偏向锁和自旋锁,所以性能上和ReentrantLock差不多,
但操作上方便很多,所以优先使用Synchronized

68.那么请谈谈AQS框架是怎么一回事?

1.AQS是AbstractQueuedSynchronized的缩写
它提供了一个FIFO队列
可以看成是一个实现同步锁的核心组件

AQS是一个抽象类,主要通过继承的方式来使用
它本身没有实现任何的同步接口,
仅仅是定义了同步状态的获取和释放的方法来提供自定义的同步组件

2.AQS的两种功能:独占锁和共享锁

3.AQS的内部实现:

AQS的实现依赖内部的同步队列,也就是FIFO的双向队列,
如果当前线程竞争失败,
那么AQS会把当前线程以及等待状态信息构造成一个Node加入到同步队列中,同时再阻塞该线程,
当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)

AQS队列内部维护的是一个FIFO的双向链表,
这种结构的特点是每个数据结构都有两个指针,
分别指向直接的后继节点和直接前驱节点。
所以双向链表可以从任意一个节点开始很方便的访问前驱和后继节点。
每个Node其实是由线程封装,
当线程争抢锁失败后会封装成Node加入到AQS队列中

69.AQS对资源的共享方式?

AQS定义两种资源共享方式
1.Exclusive(独占)
独占锁:只有一个线程能执行,如ReentrantLock。
又可分为公平锁和非公平锁
公平锁:按照线程在队列中的排列顺序,先到者先拿到锁
非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的

2.Share(共享)
多个线程可同时执行,
如SemaphoreCountDownLatchCyclicBarrierReadWriteLockReentrantReadWriteLock可以看成是组合式,
因为ReentrantReadWriteLock也就是读写锁允许多个线程同时对某一资源进行读。

不同的自定义同步器争用共享资源的方式也不同。
自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,
至于具体线程等待队列的维护,如获取资源失败入队或唤醒出队等,
AQS已经在顶层实现好了

70.如何让Java的线程彼此同步?

1.synchronized

2.volatile

3.ReentrantLock

4.使用局部变量实现线程同步

71.你了解过哪些同步器?请分别介绍下

1.Semaphore同步器
特征:
经典的信号量,通过计数器控制对共享资源的访问
Semaphore(int count):创建拥有count个许可证的信号量
acquire()/acquire(int num):获取1/num个许可证
release()/release(int num):释放1/num个许可证

2.CountDownLatch同步器
特征:
必须发生指定数量的事件后才可以继续运行(比如赛跑比赛,裁判喊出32,1之后大家才同时跑)
CountDownLatch(int count):必须发生count个数量才可以打开锁存器
await():等待锁存器
countDown():触发事件

3.CyclicBarrier同步器
特征:
适用于只有多个线程都到达预定点时才可以继续执行(比如斗地主,需要等齐三个人才开始)
CyclicBarrier(int num):等待线程的数量
CyclicBarrier(int num,Runnable action):等待线程的数量以及所有线程到达后的操作
await():到达临界点后暂停线程

4.交换器(Exchanger)同步器

5.Phaser同步器

72.Java中的线程池是如何实现的?

创建一个阻塞队列来容纳任务,
在第一次执行任务时创建足够多的线程,并处理任务,
之后每个工作线程自动从任务队列中获取线程,直到任务队列中任务为0为止,此时线程处于等待状态,
一旦有工作任务加入任务队列中,即刻唤醒工作线程进行处理,实现线程的可复用性。

线程池一般包括四个基本组成部分:
1.线程池管理器
用于创建线程池,销毁线程池,添加新任务

2.工作线程
线程池中线程,可循环执行任务,在没有任务时处于等待状态

3.任务队列
用于存放没有处理的任务,一种缓存机制。

4.任务接口
每个任务必须实现的接口,供工作线程调度任务的执行,
主要规定了任务的开始和收尾工作和任务的状态

73.创建线程池的几个核心构造参数?

Java线程池的完整构造函数

public ThreadPoolExecutor{
    int corePoolSize;// 线程池长期维持的最小线程数,即使线程处于Idle状态,也不会回收
    int maximumPoolSize;// 线程数的上限
    long keepAliveTime;// 线程最大生命周期
    TimeUnit unit;// 时间单位
    BlockingQueue<Runnable> workQueue;// 任务队列
    ThreadFactory threadFactory;// 线程工厂
    RejectedExecutionHandler handler;// 拒绝任务处理器
}

74.线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗?

线程池中的线程是在第一次提交任务submit时创建的

创建线程的方式有继承Thread和实现Runnable,重写run方法,start开始执行,wait等待,sleep休眠,shutdown停止。

(1)newSingleThreadExecutor:单线程池。

顾名思义就是一个池中只有一个线程在运行,该线程永不超时,而且由于是一个线程,当有多个任务需要处理时,会将它们放置到一个无界阻塞队列中逐个处理,它的实现代码如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
             new LinkedBlockingQueue<Runnable()));
}
它的使用方法也很简单,下面是简单的示例:

public static void main(String[] args) throws ExecutionException,InterruptedException {
    // 创建单线程执行器
    ExecutorService es = Executors.newSingleThreadExecutor();
    // 执行一个任务
    Future<String> future = es.submit(new Callable<String>() {
        @Override
        public String call() throws Exception {
            return "";
        }
    });
    // 获得任务执行后的返回值
    System.out.println("返回值:" + future.get());
    // 关闭执行器
    es.shutdown();
}2)newCachedThreadPool:缓冲功能的线程。

建立了一个线程池,而且线程数量是没有限制的(当然,不能超过Integer的最大值),新增一个任务即有一个线程处理,或者复用之前空闲的线程,或者重亲启动一个线程,但是一旦一个线程在60秒内一直处于等待状态时(也就是一分钟无事可做),则会被终止,其源码如下:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
这里需要说明的是,任务队列使用了同步阻塞队列,这意味着向队列中加入一个元素,即可唤醒一个线程(新创建的线程或复用空闲线程来处理),这种队列已经没有队列深度的概念了。

(3)newFixedThreadPool:固定线程数量的线程池。

在初始化时已经决定了线程的最大数量,若任务添加的能力超出了线程的处理能力,则建立阻塞队列容纳多余的任务,其源码如下: 

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
}
上面返回的是一个ThreadPoolExecutor,它的corePoolSize和maximumPoolSize是相等的,也就是说,最大线程数量为nThreads。如果任务增长的速度非常快,超过了LinkedBlockingQuene的最大容量(Integer的最大值),那此时会如何处理呢?会按照ThreadPoolExecutor默认的拒绝策略(默认是DiscardPolicy,直接丢弃)来处理。

以上三种线程池执行器都是ThreadPoolExecutor的简化版,目的是帮助开发人员屏蔽过得线程细节,简化多线程开发。当需要运行异步任务时,可以直接通过Executors获得一个线程池,然后运行任务,不需要关注ThreadPoolExecutor的一系列参数时什么含义。当然,有时候这三个线程不能满足要求,此时则可以直接操作ThreadPoolExecutor来实现复杂的多线程计算。

newSingleThreadExecutor、newCachedThreadPool、newFixedThreadPool是线程池的简化版,而ThreadPoolExecutor则是旗舰版___简化版容易操作,需要了解的知识相对少些,方便使用,而旗舰版功能齐全,适用面广,难以驾驭。

75.volatile关键字的作用是什么?

并发编程三问题:原子性问题,可见性问题,有序性问题

volatile关键字来保证可见性和禁止指令重排
volatile提供happens-before的保证,
确保一个线程的修改能对其他线程是可见的。
当一个共享变量被volatile修饰时,
它会保证修改的值会立即被更新到主存,
当有其他线程需要读取时,它会去内存中读取新值

从实践角度而言,volatile只能保证可见性,只能禁止指令重排序,不能保证原子性
但volatile可以和CAS结合,保证了原子性
详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger

volatile常用于多线程环境下的单次操作,
比如单次读或者单次写

76.既然volatile能够保证线程间的变量可见性,是不是就意味着基于volatile变量的运算就是并发安全的?

volatile修饰的变量在各个线程的工作内存中不存在一致性的问题
在各个线程工作的内存中,volatile修饰的变量也会存在不一致的情况
但是由于每次使用之前都会先刷新主存中的数据到工作内存,
执行引擎看不到不一致的情况,因此可以认为不存在不一致的问题
但是Java的运算并非原子性的操作,导致volatile在并发情况下并非是线程安全的

77.ThreadLocal是什么?有哪些使用场景?

ThreadLocal是一个本地线程副本变量工具类,
在每个线程中都创建了一个ThreadLocalMap对象,
简单说ThreadLocal就是一种以空间换时间的做法,
每个线程可以访问自己内部ThreadLocalMap对象内的value。
通过这种方式,避免资源在多线程间共享。

原理:线程局部变量是局限于线程内部的变量,
属于线程自身所有,不在多个线程间共享
Java提供ThreadLocal类来支持线程局部变量,是一种线程安全的方式。
但是在管理环境下(如web服务器)使用线程局部变量的时候要特别小心,
在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。
任何线程局部变量一旦在工作完成后没有释放,Java应用就存在内存泄漏的风险。

经典的使用场景是为每个线程分配一个JDBC连接Connection。
这样就可以保证每个线程都在各自的Connection上进行数据库的操作,
不会出现A线程关了B线程仍在正在使用Connection
还有Session管理等问题。

78.请谈谈ThreadLocal是怎么解决并发安全的?

Java程序中,常用的两种机制来解决多线程并发问题,
一种是synchronized方式,通过锁机制,
一个线程执行时,让另一个线程等待,是以时间换空间的方式来让多线程串行执行。
而另一种方式就是ThreadLocal方式,
通过创建线程局部变量,以空间换时间的方式来让多线程并行执行。

两种方式各有优劣,适用于不同的场景,要根据不同的业务场景来进行选择。

在Spring的源码中,就是用了ThreadLocal来管理连接,
在很多开源项目中,都经常使用ThreadLocal来控制多线程并发问题,
因为它足够的简单,我们不需要关心是否有线程安全问题,
因为变量是每个线程所特有的。

79.很多人都说要慎用ThreadLocal,谈谈你的理解,使用ThreadLocal需要注意些什么?

ThreadLocal变量解决了多线程环境下单个线程中变量的共享问题,
使用名为ThreadLocalMap的哈希表进行维护
key为ThreadLocal变量名,value为ThreadLocal变量的值

使用时需要注意的以下几点:
1.线程之间的threadLocal变量是局部变量,是互不影响的
2.使用private final static进行修饰,防止多实例时内存泄漏问题
3.线程池环境下使用后将threadLocal变量remove()掉或设置成一个初始值

80.为什么代码会重排序?

在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,
但是不能随意重排序,不是你想怎么排序怎么排序,

指令重排序需要满足以下两个条件:
1.在单线程环境下不能改变程序运行的结果
2.存在数据依赖关系的不允许重排序

需要注意的是:
重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语义。

81.什么是自旋?

很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,
此时等待的线程都加锁可能是一种不太值得的操作,
因为线程阻塞涉及到用户态和内核态切换的问题。
既然synchronized里面的代码执行得非常快,
不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,
这就是自旋。
如果做了多次循环发现还没有获得锁,
再阻塞,这样可能是一种更好的策略。

82.多线程中synchronized锁升级的原理是什么?

synchronized锁升级原理:
在锁对象的对象头里面有一个threadId字段,
在第一次访问的时候threadId为空,JVM让其持有偏向锁,
并将对象头里面的threadId设置为当前线程的id,
再次进入的时候会判断threadId是否与其线程id一致,
如果一致则可以直接使用此对象,
如果不一致,则升级偏向锁为轻量级锁,
通过自旋循环一定次数来获取锁,
执行一定次数之后,如果还没有正常获取到要使用的对象,
此时就会把锁从轻量级升级为重量级锁,
此过程就构成了synchronized锁的升级

锁的升级的目的:锁升级是为了降低锁带来的性能消耗。
在Java6后优化synchronized的实现方式,
使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,
从而降低了锁带来的性能消耗。

83.synchronized和ReentrantLock区别是什么?

synchronized是和ifelseforwhile一样的关键字
ReentrantLock是个Java类
这是两者的本质区别。
既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,
可以被继承,可以有方法,可以有各种各样的变量

synchronized早起的实现比较低效,对比ReentrantLock,
大多数场景性能都相差较大,
但是在Java6中对synchronized进行了非常多的改进

相同点:两者都是可重入锁

可重入锁的概念是:自己可以再次获取自己的内部锁
比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,
当再次想要获取这个对象的锁的时候还是可以获取的,
如果是不可重入锁的话,就会造成死锁。
同一个线程每次获取锁,锁的计数器都自增1,
所以要等到锁的计数器下降为0时才能释放锁。

主要区别如下:
1.ReentrantLock使用起来比较灵活,但是必须有释放锁的配合动作
2.ReentrantLock必须手动获取与释放锁,而synchronized不需要手动释放和开启锁
3.ReentrantLock只适用于代码块锁,而synchronized可以修饰类、方法、变量等
4.二者的锁机制其实也是不一样的。
ReentrantLock底层调用的是Unsafepark()方法加锁
synchronized操作的应该是对象头中mark word

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
1.普通同步方法,锁是当前实例对象
2.静态同步方法,锁是当前类的class对象
3.同步方法块,锁是括号里面的对象

84.Java Concurrency API中的Lock接口(Lock Interface)是什么?对比同步它有什么优势?

Lock接口比同步方法和同步代码块提供了更具扩展性的操作。
他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象

Lock接口的优势有:
1.可以使锁更公平
2.可以使线程在等待锁的时候响应中断
3.可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间
4.可以在不同的范围,以不同的顺序获取和释放锁

整体上来说Locksynchronized的扩展板,
Lock提供了无条件的、可轮询的(tryLock()方法)、定时的(tryLock(参数)带参方法)、
可中断的(lockInterruptibly()方法)、可多条件队列的(newCondition()方法)锁操作。
另外Lock的实现类基本都支持非公平锁(默认)和公平锁,
synchronized只支持非公平锁,
当然在大部分情况下,非公平锁是高效的选择。

85.Java中什么时候用重载?什么时候用重写?

重载是多态的集中体现,在同一个类中,要以统一的方式处理不同类型数据的时候,可以用重载。

重写的使用是建立在继承关系上的,子类在继承父类的基础上,增加新的功能,可以用重写。

总结:
重载是多样性,重写是增强剂;
重载是为了同一业务提供多种解决方案,目的是提高程序的多样性和健壮性,以适配不同场景使用时,使用重载进行扩展;
重写是对指定的某个方案进行增强,目的是在不修改原方法及源代码的基础上对方法进行扩展或增强时,使用重写;

生活例子:
你想吃一碗面,我给你提供了拉面,炒面,刀削面,担担面供你选择,这是重载;
你想吃一碗面,我不但给你端来了面,还给你加了青菜,加了鸡蛋,这个是重写;

设计模式:
cglib实现动态代理,核心原理用的就是方法的重写;

详细解答:

Java的重载(overload) 最重要的应用场景就是构造器的重载
构造器重载后,提供多种形参形式的构造器,可以应对不同的业务需求,加强程序的健壮性和可扩展性,
比如我们最近学习的Spring源码中的ClassPathXmlApplicationContext,
它的构造函数使用重载一共提供了10个构造函数,这样就为业务的选择提供了多选择性。
在应用到方法中时,主要是为了增强方法的健壮性和可扩展性,比如我们在开发中常用的各种工具类,
比如我目前工作中的短信工具类SMSUtil, 发短信的方法就会使用重载,
针对不同业务场景下的不同形参,提供短信发送方法,这样提高了工具类的扩展性和健壮性。

总结:重载必须要修改方法(构造器)的形参列表,可以修改方法的返回值类型,也可以修改方法的异常信息即访问权限;
使用范围是在同一个类中,目的是提供同一业务的多种解决方案的选择,以应对多业务场景的不同使用需求。提高程序的健壮性和扩展性。

java的重写(override) 只要用于子类对父类方法的扩展或修改,重写的前提是有继承关系
但是在我们开发中,为了避免程序混乱,重写一般都是为了方法的扩展,
比如在cglib方式实现的动态代理中,代理类就是继承了目标类,对目标类的方法进行重写,同时在方法前后进行切面织入。

总结:
重载和重写都允许你用相同的名称来实现不同的功能,
但重载是编译时活动,重写是运行时活动
重写:重写的方法和被重写的方法必须具有相同的方法名、参数列表和返回类型
重写方法不能使用比被重写方法更严格的访问权限,异常可以减少或者删除,但是不能抛出新的异常或者更广的异常,
重载:方法名要一样,但是参数类型和个数不一样,返回值类型可以相同也可以不同
无法根据返回值类型作为重载方法的区分标准

(4)在里氏替换原则中,子类对父类的方法尽量不要重写和重载。(我们可以采用final的手段强制来遵循)

86.简述Collections.sort()排序内部原理?

Java 6Arrays.sort()Collections.sort()使用的是MergeSort,
而在Java 7中,内部实现换成了TimSort,其对对象间比较的实现要求更加严格

87.String和StringBuilder有什么区别?

一:是否可变
String不可变,每执行一次"+"拼接操作都会洗生成一个新对象
所以频繁改变字符串不用String,为了节省内存
StringBuilder可以改变,可以拼接

二:是否多线程安全
StringStringBuffer是线程安全的
StringBuidler并没有对方法加同步锁,所以是非线程安全的

88.Vector与ArrayList有哪些区别?

一:扩容规则
ArrayList在内存不够时默认是扩展50%+1Vector是默认扩展1倍
二:是否多线程安全
Vector属于线程安全的,不用的原因是线程安全需要更大的系统开销
ArrayList线程不安全

89.HashMap与HashTable有什么区别?

一:继承父类
HashMap继承自AbstractMap
HashTable继承自Dictonary类

二:是否允许为null
HashMap允许空的键值对,但最多只允许有一个null
HashTable不允许为null

三:是否多线程安全
HashMap线程不安全,但效率比HashTableHashTable线程安全

90.ArrayList与LinkedList有什么区别?

一:底层数据结构
ArrayList底层是数组,支持随机访问
LinkedList底层是双向链表

二:长处
ArrayList支持随机访问,查询快
LinkedList不支持随机访问,增删效率高

三:时间复杂度
ArrayList的时间复杂度是O(1)
LinkedList的时间复杂度是O(n)

91.Comparable与Comparator有什么不同?

Comparable接口用于定义对象的自然顺序,是排序接口
Comparator用于用户定制的顺序,是比较接口

我们如果需要控制某个类的次序,而类本身不支持排序(即没有实现Comparable接口),
那么我们可以建立一个该类的比较器来进行排序
Comparable总是只有一个
Comparator可以有多个来定义对象的顺序

92.Collection与Collections的区别是什么?

CollectionJava集合框架中的基本接口
CollectionsJava集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法

93.Java中多态的实现原理?

所谓多态就是,父类引用指向子类对象,调用方法时会调用子类的实现而不是父类的实现
多态实现的关键在于“动态绑定”

94.Object中定义了哪些方法?

Object中定义了九个方法:
1.getClass()
2.clone()
3.toString()
4.equals()
5.hashCode()
6.wait()
7.notify()
8.notifyAll()
9.finalize()

95.Java泛型和类型擦除?

泛型即参数化类型,在创建集合时,指定集合元素的类型,此集合只能传入该类型的参数

类型擦除:Java编器生成的字节码文件不包括泛型信息,
所以在编译时擦除:
1.泛型用最顶级父类替换
2.移除

96.说出5个JDK8引入的新特性

举例JDK8中引入的五个新特性:
一:Lambda表达式
二:允许像对象一样传递匿名函数Stream API,充分利用多核CPU
三:DateTime API,有一个稳定、简单的日期和时间库可供你使用扩展方法
四:接口中可以有静态、默认方法
五:重复注解:可以将相同的注解在同一类型上使用多次

97.常用的数据结构有哪些?

集合
线性结构:数组、队列、链表和栈
树形结构
图状结构

98.Java中的TreeMap是采用什么树实现的?

Java中的TreeMap是使用红黑树实现的

99.匿名内部类是什么?如何访问在其外面定义的变量?

匿名内部类也就是没有名字的内部类,
匿名内部类只能使用一次,它通常用来简化代码编写

匿名内部类只能访问外部类的final变量,
Java8更加智能:如果局部变量被匿名内部类访问,那么该局部变量相当于自动使用了final修饰

100.写一段代码遍历ArrayList移除一个元素

//在遍历 ArrayList 移除元素时,需要注意的是不能直接调用 remove 方法,因为这会导致遍历过程中的迭代器失效,从而导致抛出 ConcurrentModificationException 异常。正确的做法是使用迭代器的 remove 方法,在遍历过程中移除元素。以下是示例代码:

ArrayList<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");

Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
    String str = iterator.next();
    if(str.equals("Java")) {
        iterator.remove();
    }
}

System.out.println(list);
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值