第一章-Java基础篇
1. Java中的基本数据类型有哪些?⭐
Java中有8种基本数据类型(Primitive Types),分别是:
- byte:8位,-128 ~ 127
- short:16位,-32,768 ~ 32,767
- int:32位,-2^31 ~ 2^31-1
- long:64位,-2^63 ~ 2^63-1
- float:32位,单精度浮点数
- double:64位,双精度浮点数
- char:16位,表示单一字符,范围0~65535(Unicode字符)
- boolean:只有两个值,
true
或false
2. 什么是Java中的多态?⭐
多态是面向对象编程中的一个重要概念,它允许方法有多种表现形式。在Java中,多态主要通过**方法重载(Overloading)和方法重写(Overriding)**来实现。
- 方法重载:在同一个类中,方法名相同,但参数不同。方法重载是在编译时发生的,属于编译时多态。
- 方法重写:子类重写父类的方法,方法签名必须相同。方法重写是在运行时发生的,属于运行时多态。
3. Java中的内存模型及垃圾回收(GC)机制⭐
- JVM内存模型:JVM内存分为堆(Heap)和栈(Stack)。堆用于存储对象实例,栈用于存储局部变量和方法调用。
- 垃圾回收机制:垃圾回收(GC)是JVM自动管理内存的过程,它会回收不再使用的对象。GC采用分代收集的策略,将内存划分为年轻代、老年代和永久代(Metaspace),使用不同的回收算法(如标记-清除、复制算法)回收不同代的对象。
4. 什么是final
、finally
和finalize
?⭐
-
final:用于修饰类、方法和变量:
final
类不能被继承。final
方法不能被重写。final
变量的值不能改变。
-
finally:用于异常处理中的代码块,无论是否发生异常,
finally
块中的代码都会执行。通常用于资源的释放。 -
finalize:是
Object
类中的一个方法,用于在对象被垃圾回收前进行清理。这个方法并不常用,且并不是立即调用,因此不推荐使用。
5. 什么是线程安全?⭐
线程安全是指多个线程同时访问某个类或对象时,不会出现数据不一致的情况。在Java中,可以通过以下方式实现线程安全:
- 使用同步(synchronized):通过
synchronized
关键字对方法或代码块加锁,确保同一时间只有一个线程能够执行被同步的代码。 - 使用显式锁(Lock):如
ReentrantLock
,提供比synchronized
更灵活的锁机制。 - 使用线程安全的集合类:如
Vector
、ConcurrentHashMap
等。 - 使用
volatile
关键字:确保共享变量的可见性。
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
6. Java中的异常处理机制(Checked Exception与Unchecked Exception)⭐
- Checked Exception:继承自
Exception
类,但不继承RuntimeException
类,必须通过try-catch
块处理或通过throws
声明抛出。- 例:
IOException
、SQLException
。
- 例:
- Unchecked Exception:继承自
RuntimeException
类,通常是程序中的逻辑错误,不需要强制捕获。- 例:
NullPointerException
、ArrayIndexOutOfBoundsException
。
- 例:
7. HashMap
和ConcurrentHashMap
的区别⭐
HashMap
:非线程安全的集合类,允许null
键和值,通常用于单线程环境或需要手动同步的多线程环境。ConcurrentHashMap
:线程安全的集合类,通过分段锁技术实现高效的并发访问,适用于多线程环境。它允许并发读取,同时保证写操作的线程安全。
8. Java中的深拷贝与浅拷贝⭐
- 浅拷贝(Shallow Copy):复制对象时,只复制对象的引用类型的成员变量的引用,而不是引用对象本身。常用
Object.clone()
方法进行浅拷贝。 - 深拷贝(Deep Copy):复制对象时,不仅复制对象本身,还会递归地复制引用对象,确保对象完全独立。
9. 什么是volatile
关键字?⭐
volatile
是一个Java关键字,用于标记一个变量为易变的,确保线程对该变量的写入操作对其他线程可见。volatile
的使用可以确保变量在多个线程之间的同步。
volatile
保证了变量的可见性,即当一个线程修改了该变量的值,其他线程立即能看到这个改变。volatile
不保证原子性,多个线程对volatile
变量进行操作时仍需考虑线程安全问题。
10. 线程池的作用和常见实现类⭐
线程池通过复用已有的线程来避免频繁创建和销毁线程,提高程序的性能和资源的利用率。常用的线程池类有:
ExecutorService
:接口,提供执行任务的方法。ThreadPoolExecutor
:ExecutorService
的实现类,提供了更加详细的配置参数,适用于大多数场景。ScheduledThreadPoolExecutor
:用于定时执行任务。
11. synchronized
与ReentrantLock
的区别⭐
synchronized
:- 内置锁,语法简洁。
- 自动释放锁。
- 不支持尝试锁(tryLock())和定时锁。
ReentrantLock
:- 显式锁,功能更强大,支持尝试锁、定时锁等高级功能。
- 需要手动释放锁。
- 适用于需要更细粒度控制的场景。
12、你是怎样理解OOP面向对象 难度系数:⭐
面向对象是利于语言对现实事物进行抽象。面向对象具有以下特征:
- 1、继承:继承是从已有类得到继承信息创建新类的过程
- 2、封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口
- 3、多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应
13、重载与重写区别 难度系数:⭐
- 重载发生在本类,重写发生在父类与子类之间
- 重载的方法名必须相同,重写的方法名相同且返回值类型必须相同
- 重载的参数列表不同,重写的参数列表必须相同
- 重写的访问权限不能比父类中被重写的方法的访问权限更低
- 构造方法不能被重写
14、接口与抽象类的区别 难度系数:⭐
- 抽象类要被子类继承,接口要被类实现
- 接口可多继承接口,但类只能单继承
- 抽象类可以有构造器、接口不能有构造器
- 抽象类:除了不能实例化抽象类之外,它和普通Java类没有任何区别
- 抽象类:抽象方法可以有public、protected和default这些修饰符、接口:只能是public
- 抽象类:可以有成员变量;接口:只能声明常量
15、sleep和wait区别 难度系数:⭐
- sleep方法
- 属于Thread类中的方法
- 释放cpu给其它线程 不释放锁资源
- sleep(1000) 等待超过1s被唤醒
- wait方法
- 属于Object类中的方法
- 释放cpu给其它线程,同时释放锁资源
- wait(1000) 等待超过1s被唤醒
- wait() 一直等待需要通过notify或者notifyAll进行唤醒
- wait 方法必须配合 synchronized 一起使用,不然在运行时就会抛出IllegalMonitorStateException异常
-
#### 锁释放时机代码演示
-
public static void main(String[] args) {
-
Object o = new Object();
-
Thread thread = new Thread(() -> {
-
synchronized (o) {
-
System.out.println("新线程获取锁时间:" + LocalDateTime.now() + " 新线程名称:" + Thread.currentThread().getName());
-
try {
-
-
//wait 释放cpu同时释放锁
-
o.wait(1000);
-
-
//sleep 释放cpu不释放锁
-
//Thread.sleep(1000);
-
System.out.println("新线程获取释放锁锁时间:" + LocalDateTime.now() + " 新线程名称:" + Thread.currentThread().getName());
-
} catch (InterruptedException e) {
-
throw new RuntimeException(e);
-
}
-
}
-
});
-
-
thread.start();
-
-
try {
-
Thread.sleep(100);
-
} catch (InterruptedException e) {
-
throw new RuntimeException(e);
-
}
-
-
System.out.println("主线程获取锁时间:" + LocalDateTime.now() + " 主线程名称:" + Thread.currentThread().getName());
-
-
synchronized (o){
-
System.out.println("主线程获取释放锁锁时间:" + LocalDateTime.now() + " 主线程名称:" + Thread.currentThread().getName());
-
}
-
}
16、什么是自动拆装箱 int和Integer有什么区别 难度系数:⭐
基本数据类型,如int,float,double,boolean,char,byte,不具备对象的特征,不能调用方法。
- 装箱:将基本类型转换成包装类对象
- 拆箱:将包装类对象转换成基本类型的值
java为什么要引入自动装箱和拆箱的功能?主要是用于java集合中,List<Inteter> list=new ArrayList<Integer>();
list集合如果要放整数的话,只能放对象,不能放基本类型,因此需要将整数自动装箱成对象。
实现原理:javac编译器的语法糖,底层是通过Integer.valueOf()和Integer.intValue()方法实现。
区别:
- Integer是int的包装类,int则是java的一种基本数据类型
- Integer变量必须实例化后才能使用,而int变量不需要
- Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
- Integer的默认值是null,int的默认值是0
-
17、==和equals区别 难度系数:⭐
- ==
如果比较的是基本数据类型,那么比较的是变量的值
如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内存)
- equals
如果没重写equals方法比较的是两个对象的地址值
如果重写了equals方法后我们往往比较的是对象中的属性的内容
equals方法是从Object类中继承的,默认的实现就是使用==
18、String能被继承吗 为什么用final修饰 难度系数:⭐
- 不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。
- String 类是最常用的类之一,为了效率,禁止被继承和重写。
- 为了安全。String 类中有native关键字修饰的调用系统级别的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。Java 的安全性也体现在这里。
19、String buffer和String builder区别 难度系数:⭐
- StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,
- 只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。
- 在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低
20、Object中有哪些方法 难度系数:⭐
- protected Object clone()--->创建并返回此对象的一个副本。
- boolean equals(Object obj)--->指示某个其他对象是否与此对象“相等
- protected void finalize()--->当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
- Class<? extendsObject> getClass()--->返回一个对象的运行时类。
- int hashCode()--->返回该对象的哈希码值。
- void notify()--->唤醒在此对象监视器上等待的单个线程。
- void notifyAll()--->唤醒在此对象监视器上等待的所有线程。
- String toString()--->返回该对象的字符串表示。
- void wait()--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout)--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll()方法,或者超过指定的时间量。
void wait(long timeout, int nanos)--->导致当前的线程等待,直到其他线程调用此对象的 notify()
21、说一下集合体系 难度系数:⭐

22、ArrarList和LinkedList区别 难度系数:⭐
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问get和set,ArrayList效率优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。
23、HashMap底层是 数组+链表+红黑树,为什么要用这几类结构 难度系数:⭐⭐
- 数组 Node<K,V>[] table ,哈希表,根据对象的key的hash值进行在数组里面是哪个节点
- 链表的作用是解决hash冲突,将hash值取模之后的对象存在一个链表放在hash值对应的槽位
- 红黑树 JDK8使用红黑树来替代超过8个节点的链表,主要是查询性能的提升,从原来的O(n)到O(logn),
- 通过hash碰撞,让HashMap不断产生碰撞,那么相同的key的位置的链表就会不断增长,当对这个Hashmap的相应位置进行查询的时候,就会循环遍历这个超级大的链表,性能就会下降,所以改用红黑树
24、 什么是JVM,JRE,JDK?⭐
- JVM(Java Virtual Machine):Java虚拟机,是Java程序运行时的解释执行环境。JVM负责加载、解释和执行Java字节码。
- JRE(Java Runtime Environment):Java运行时环境,是JVM与类库的结合。JRE包含了JVM以及Java程序运行所需的库和文件,但不包含编译工具。
- JDK(Java Development Kit):Java开发工具包,包含了JRE和开发Java程序所需的工具,如编译器(javac)、调试器(jdb)、文档工具(javadoc)等。
25、HashMap和HashTable区别 难度系数:⭐
- 线程安全性不同
- HashMap是线程不安全的,HashTable是线程安全的,其中的方法是Synchronized,在多线程并发的情况下,可以直接使用HashTable,但是使用HashMap时必须自己增加同步处理。
- 是否提供contains方法
- HashMap只有containsValue和containsKey方法;HashTable有contains、containsKey和containsValue三个方法,其中contains和containsValue方法功能相同。
- key和value是否允许null值
- Hashtable中,key和value都不允许出现null值。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
- 数组初始化和扩容机制
- HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。
- Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。
26、线程的创建方式 难度系数:⭐
继承Thread类创建线程
实现Runnable接口创建线程
使用Callable和Future创建线程 有返回值
使用线程池创建线程
-
# 代码演示
-
import java.util.concurrent.*;
-
public class threadTest{
-
public static void main(String[] args) throws ExecutionException, InterruptedException {
-
//继承thread
-
ThreadClass thread = new ThreadClass();
-
thread.start();
-
Thread.sleep(100);
-
System.out.println("#####################");
-
-
//实现runnable
-
RunnableClass runnable = new RunnableClass();
-
new Thread(runnable).start();
-
Thread.sleep(100);
-
System.out.println("#####################");
-
-
//实现callable
-
FutureTask futureTask = new FutureTask(new CallableClass());
-
futureTask.run();
-
System.out.println("callable返回值:" + futureTask.get());
-
Thread.sleep(100);
-
System.out.println("#####################");
-
-
//线程池
-
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
-
threadPoolExecutor.execute(thread);
-
threadPoolExecutor.shutdown();
-
Thread.sleep(100);
-
System.out.println("#####################");
-
-
//使用并发包Executors
-
ExecutorService executorService = Executors.newFixedThreadPool(5);
-
executorService.execute(thread);
-
executorService.shutdown();
-
}
-
}
-
-
class ThreadClass extends Thread{
-
@Override
-
public void run() {
-
System.out.println("我是继承thread形式:" + Thread.currentThread().getName());
-
}
-
}
-
-
class RunnableClass implements Runnable{
-
@Override
-
public void run(){
-
System.out.println("我是实现runnable接口:" + Thread.currentThread().getName());
-
}
-
}
-
-
class CallableClass implements Callable<String> {
-
@Override
-
public String call(){
-
System.out.println("我是实现callable接口:");
-
return "我是返回值,可以通过get方法获取";
-
}
-
}
27、线程的状态转换有什么(生命周期) 难度系数:⭐
- 新建状态(New) :线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
- 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
- 运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
- 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
- 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
28、请写出你最常见的5个RuntimeException 难度系数:⭐
- java.lang.NullPointerException
- 空指针异常;出现原因:调用了未经初始化的对象或者是不存在的对象。
- java.lang.ClassNotFoundException
- 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常。
- java.lang.NumberFormatException
- 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
- java.lang.IndexOutOfBoundsException
- 数组角标越界异常,常见于操作数组对象时发生。
- java.lang.IllegalArgumentException
- 方法传递参数错误。
- java.lang.ClassCastException
- 数据类型转换异常。
29、谈谈你对反射的理解 难度系数:⭐
- 反射机制
所谓的反射机制就是java语言在运行时拥有一项自观的能力。通过这种能力可以彻底了解自身的情况为下一步的动作做准备。
反射是Java的一项强大功能,允许在运行时查询类的信息并操作类的属性、方法、构造函数等。通过反射,可以动态创建对象、调用方法或修改字段,常见的类有Class
、Method
、Field
等。
- Java反射的作用
在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。
- Java 反射机制提供功能
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题
需要全套面试笔记【点击此处即可】免费获取
30、什么是 java 序列化,如何实现 java 序列化 难度系数:⭐
- 序列化是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
- 序 列 化 的 实 现 : 将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口 , 该 接 口 没 有 需 要 实 现 的 方 法 , implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。
31. 什么是垃圾回收(Garbage Collection)?
垃圾回收(GC)是JVM自动管理内存的一部分,它会在程序运行时自动清理不再使用的对象,释放内存。JVM会定期执行垃圾回收操作,主要包括:
- 标记清除:标记所有需要回收的对象,并清除它们。
- 复制算法:将存活的对象从一个内存区域复制到另一个区域。
- 分代收集:将堆内存分为年轻代、老年代,分别采用不同的回收策略。
32、Http 常见的状态码 难度系数:⭐
- 200 OK //客户端请求成功
- 301 Permanently Moved (永久移除),请求的 URL 已移走。Response 中应该包含一个 Location URL, 说明资源现在所处的位置
- 302 Temporarily Moved 临时重定向
- 400 Bad Request //客户端请求有语法错误,不能被服务器所理解
- 401 Unauthorized //请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用
- 403 Forbidden //服务器收到请求,但是拒绝提供服务
- 404 Not Found //请求资源不存在,eg:输入了错误的 URL
- 500 Internal Server Error //服务器发生不可预期的错误
- 503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
33、GET 和POST 的区别 难度系数:⭐
- GET 请求的数据会附在URL 之后(就是把数据放置在 HTTP 协议头中),以?分割URL 和传输数据,参数之间以&相连,如:login.action?name=zhagnsan&password=123456。POST 把提交的数据则放置在是 HTTP 包的包体中。
- GET 方式提交的数据最多只能是 1024 字节,理论上POST 没有限制,可传较大量的数据。其实这样说是错误的,不准确的:“GET 方式提交的数据最多只能是 1024 字节",因为 GET 是通过 URL 提交数据,那么 GET 可提交的数据量就跟URL 的长度有直接关系了。而实际上,URL 不存在参数上限的问题,HTTP 协议规范没有对 URL 长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE 对URL 长度的限制是2083 字节(2K+35)。对于其他浏览器,如Netscape、FireFox 等,理论上没有长度限制,其限制取决于操作系统的支持。
- POST 的安全性要比GET 的安全性高。注意:这里所说的安全性和上面 GET 提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的 Security 的含义,比如:通过 GET 提交数据,用户名和密码将明文出现在 URL 上,因为(1)登录页面有可能被浏览器缓存,(2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,除此之外,使用 GET 提交数据还可能会造成 Cross-site request forgery 攻击。
- Get 是向服务器发索取数据的一种请求,而 Post 是向服务器提交数据的一种请求,在 FORM(表单)中,Method
- 默认为"GET",实质上,GET 和 POST 只是发送机制不同,并不是一个取一个发!
34、Cookie 和Session 的区别 难度系数:⭐
- Cookie 是 web 服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每个 web 服务器存储 cookie。以后浏览器再给特定的 web 服务器发送请求时,同时会发送所有为该服务器存储的 cookie
- Session 是存储在 web 服务器端的一块信息。session 对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去
- Cookie 和session 的不同点
- 无论客户端做怎样的设置,session 都能够正常工作。当客户端禁用 cookie 时将无法使用 cookie
- 在存储的数据量方面:session 能够存储任意的java 对象,cookie 只能存储 String 类型的对象
35. String 类的常用方法都有那些?
-
indexOf():返回指定字符的索引。
-
charAt():返回指定索引处的字符。
-
replace():字符串替换。
-
trim():去除字符串两端空白。
-
split():分割字符串,返回一个分割后的字符串数组。
-
getBytes():返回字符串的 byte 类型数组。
-
length():返回字符串长度。
-
toLowerCase():将字符串转成小写字母。
-
toUpperCase():将字符串转成大写字符。
-
substring():截取字符串。
-
equals():字符串比较。
第二章-Java高级篇
1. Collection 和 Collections 有什么区别?⭐
-
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
-
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
2、如何判断一个对象是否存活(或者GC对象的判定方法) 难度系数:⭐
- 引用计数法
- 所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收.
- 引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用者对象A,那么此时A,B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。
- 可达性算法(引用链法)
- 该算法的基本思路就是通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到GC Roots没有任何引用链相连时(即从GC Roots节点到该节点不可达),则证明该对象是不可用的。
- 在java中可以作为GC Roots的对象有以下几种:虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量池引用的对象、本地方法栈JNI引用的对象。
3、什么情况下会产生StackOverflowError(栈溢出)和OutOfMemoryError(堆溢出)怎么排查 难度系数:⭐⭐
引发 StackOverFlowError 的常见原因有以下几种
- 无限递归循环调用(最常见)
- 执行了大量方法,导致线程栈空间耗尽
- 方法内声明了海量的局部变量
- native 代码有栈上分配的逻辑,并且要求的内存还不小,比如 java.net.SocketInputStream.read0 会在栈上要求分配一个 64KB 的缓存(64位 Linux)。
引发 OutOfMemoryError的常见原因有以下几种
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据
- 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收
- 代码中存在死循环或循环产生过多重复的对象实体
- 启动参数内存值设定的过小
- 排查:可以通过jvisualvm进行内存快照分析
参考Java内存溢出OutOfMemoryError的产生与排查 - 牛初九 - 博客园
栈溢出、堆溢出案例演示
-
public class StackOverFlowTest {
-
private static int count = 1;
-
public static void main(String[] args) {
-
//模拟栈溢出
-
//getDieCircle();
-
-
//模拟堆溢出
-
getOutOfMem();
-
}
-
public static void getDieCircle(){
-
System.out.println(count++);
-
getDieCircle();
-
}
-
public static void getOutOfMem(){
-
while (true) {
-
Object o = new Object();
-
System.out.println(o);
-
}
-
}
-
}
4、什么是线程池,线程池有哪些(创建) 难度系数:⭐
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节省了开辟子线程的时间,提高的代码执行效率
在 JDK 的 java.util.concurrent.Executors 中提供了生成多种线程池的静态方法。
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
然后调用他们的 execute 方法即可。
这4种线程池底层 全部是ThreadPoolExecutor对象的实现,阿里规范手册中规定线程池采用ThreadPoolExecutor自定义的,实际开发也是。
- newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。这种类型的线程池特点是:
工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。
- newFixedThreadPool
创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。
- newSingleThreadExecutor
创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
- newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行。例如延迟3秒执行。
5、为什么要使用线程池 难度系数:⭐
- 线程池做的工作主要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最 大数量,超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
- 主要特点:线程复用;控制最大并发数:管理线程。
第一:降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进 行统一的分配,调优和监控。
6、线程池底层工作原理 难度系数:⭐
第一步:线程池刚创建的时候,里面没有任何线程,等到有任务过来的时候才会创建线程。当然也可以调用 prestartAllCoreThreads() 或者 prestartCoreThread() 方法预创建corePoolSize个线程
第二步:调用execute()提交一个任务时,如果当前的工作线程数<corePoolSize,直接创建新的线程执行这个任务
第三步:如果当时工作线程数量>=corePoolSize,会将任务放入任务队列中缓存
第四步:如果队列已满,并且线程池中工作线程的数量<maximumPoolSize,还是会创建线程执行这个任务
第五步:如果队列已满,并且线程池中的线程已达到maximumPoolSize,这个时候会执行拒绝策略,JAVA线程池默认的策略是AbortPolicy,即抛出RejectedExecutionException异常
7、ThreadPoolExecutor对象有哪些参数 怎么设定核心线程数和最大线程数 拒绝策略有哪些 难度系数:⭐⭐
参数与作用:共7个参数
corePoolSize:核心线程数,
在ThreadPoolExecutor中有一个与它相关的配置:allowCoreThreadTimeOut(默认为false),当allowCoreThreadTimeOut为false时,核心线程会一直存活,哪怕是一直空闲着。而当allowCoreThreadTimeOut为true时核心线程空闲时间超过keepAliveTime时会被回收。
maximumPoolSize:最大线程数
线程池能容纳的最大线程数,当线程池中的线程达到最大时,此时添加任务将会采用拒绝策略,默认的拒绝策略是抛出一个运行时错误(RejectedExecutionException)。值得一提的是,当初始化时用的工作队列为LinkedBlockingDeque时,这个值将无效。
keepAliveTime:存活时间
当非核心空闲超过这个时间将被回收,同时空闲核心线程是否回收受allowCoreThreadTimeOut影响。
unit:keepAliveTime的单位。
workQueue:任务队列
常用有三种队列,即SynchronousQueue,LinkedBlockingDeque(无界队列),ArrayBlockingQueue(有界队列)。
threadFactory:线程工厂,
ThreadFactory是一个接口,用来创建worker。通过线程工厂可以对线程的一些属性进行定制。默认直接新建线程。
RejectedExecutionHandler:拒绝策略
也是一个接口,只有一个方法,当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution法。默认是抛出一个运行时异常。
线程池大小设置:
- 需要分析线程池执行的任务的特性: CPU 密集型还是 IO 密集型
- 每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉及到网络传输以及底层系统资源依赖有关系
如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这种任务 cpu的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU 核心数=最大同时执行线程数,加入 CPU 核心数为 4,那么服务器最多能同时执行 4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数+1 如果是 IO 密集型,主要是进行 IO 操作,执行 IO 操作的时间较长,这是 cpu 出于空闲状态,导致 cpu 的利用率不高,这种情况下可以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断,等待时间越高,那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。
一个公式:线程池设定最佳线程数目 = ((线程池设定的线程等待时间+线程 CPU 时间)/
线程 CPU 时间 )* CPU 数目
这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用 loadrunner测试大量运行次数求出平均值)
拒绝策略:
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务
8、常见线程安全的并发容器有哪些 难度系数:⭐
- CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
- CopyOnWriteArrayList、CopyOnWriteArraySet采用写时复制实现线程安全
- ConcurrentHashMap采用分段锁的方式实现线程安全
9、多线程的价值?⭐
- 发挥多核 CPU 的优势
多线程,可以真正发挥出多核 CPU 的优势来,达到充分利用 CPU 的目的,采用多线程的方式去同时完成几件事情而不互相干扰。
- 防止阻塞
从程序运行效率的角度来看,单核 CPU 不但不会发挥出多线程的优势,反而会因为在单核CPU 上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核 CPU 我们还是要应用多线程,就是为了防止阻塞。试想,如果单核 CPU 使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行, 哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
- 便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务 A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务 A 分解成几个小任务,任务 B、任务 C、任务 D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
10、线程池的优点?⭐
- 重用存在的线程,减少对象创建销毁的开销。
- 可有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
- 提供定时执行、定期执行、单线程、并发数控制等功能。
11、Atomic原子类了解多少 原理是什么 难度系数:⭐
基本类型
- 使用原子的方式更新基本类型
- AtomicInteger:整型原子类
- AtomicLong:长整型原子类
- AtomicBoolean:布尔型原子类
数组类型
- 使用原子的方式更新数组里的某个元素
- AtomicIntegerArray:整形数组原子类
- AtomicLongArray:长整形数组原子类
- AtomicReferenceArray:引用类型数组原子类
引用类型
- AtomicReference:引用类型原子类
- AtomicStampedReference:原子更新引用类型里的字段原子类
- AtomicMarkableReference :原子更新带有标记位的引用类型
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
- AtomicLongFieldUpdater:原子更新长整形字段的更新器
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,以及解决使用 CAS 进行原子更新时可能出现的 ABA 问题
- AtomicInteger 类利用 CAS (Compare and Swap) + volatile + native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
- CAS 的原理,是拿期望值和原本的值作比较,如果相同,则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是个本地方法,这个方法是用来拿“原值”的内存地址,返回值是 valueOffset;另外,value 是一个 volatile 变量,因此 JVM 总是可以保证任意时刻的任何线程总能拿到该变量的最新值。
12、synchronized底层实现是什么 lock底层是什么 有什么区别 难度系数:⭐⭐⭐
Synchronized原理:
方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词),然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。
代码块的同步是利用monitorenter和monitorexit这两个字节码指令。它们分别位于同步代码块的开始和结束位置。当jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,直到其他线程释放锁。