Java面试题大全
从基础到高级,全面覆盖Java面试知识点
最后更新:2025-05-01
目录
- 第一部分:Java基础
- 第二部分:面向对象
- 第三部分:Java集合
- 第四部分:Java多线程与并发
- 第五部分:JVM虚拟机
- 第六部分:Spring框架
- 第七部分:数据库
- 第八部分:Redis缓存
- 第九部分:分布式与微服务
- 第十部分:设计模式
- 第十一部分:算法与数据结构
第一部分:Java基础
1.1 数据类型
Q1: Java有哪些基本数据类型?各占多少字节?
| 类型 | 字节 | 位数 | 取值范围 |
|---|---|---|---|
| byte | 1 | 8 | -128 ~ 127 |
| short | 2 | 16 | -32768 ~ 32767 |
| int | 4 | 32 | -2^31 ~ 2^31-1 |
| long | 8 | 64 | -2^63 ~ 2^63-1 |
| float | 4 | 32 | IEEE 754 |
| double | 8 | 64 | IEEE 754 |
| char | 2 | 16 | 0 ~ 65535 |
| boolean | 1 | 8 | true/false |
包装类对应关系:
- byte → Byte
- short → Short
- int → Integer
- long → Long
- float → Float
- double → Double
- char → Character
- boolean → Boolean
Q2: int和Integer有什么区别?
// 基本类型
int a = 10;
// 包装类型
Integer b = new Integer(10);
Integer c = Integer.valueOf(10); // 推荐方式,有缓存
// 自动装箱/拆箱
Integer d = 10; // 自动装箱:Integer.valueOf(10)
int e = d; // 自动拆箱:d.intValue()
// 缓存范围:-128 ~ 127
Integer f1 = 100;
Integer f2 = 100;
System.out.println(f1 == f2); // true,使用缓存
Integer g1 = 200;
Integer g2 = 200;
System.out.println(g1 == g2); // false,超出缓存范围
答案要点:
- int是基本类型,Integer是包装类
- Integer提供了更多方法(parseInt、toString等)
- Integer有缓存机制(-128~127)
- 泛型只能使用Integer
Q3: String能被继承吗?为什么?
public final class String implements java.io.Serializable,
Comparable<String>, CharSequence {
// ...
}
答案:不能,String被final修饰。
原因:
- 安全性:String常用于敏感信息(URL、文件路径、密码),不可继承避免被恶意篡改
- 效率:不可变性支持字符串常量池,节省内存
- 线程安全:不可变对象天生线程安全
- 哈希缓存:hashCode可以缓存,提高Map性能
1.2 字符串
Q4: String、StringBuilder、StringBuffer的区别?
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 不安全 | 安全(synchronized) |
| 性能 | 低(频繁创建) | 高 | 中 |
| 适用场景 | 少量字符串操作 | 单线程大量操作 | 多线程大量操作 |
// String:每次修改都创建新对象
String s1 = "Hello";
s1 = s1 + " World"; // 创建了新对象
// StringBuilder:原地修改
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 同一对象
// StringBuffer:线程安全版本
StringBuffer sbf = new StringBuffer("Hello");
sbf.append(" World");
Q5: String s = new String(“abc”)创建了几个对象?
答案:1个或2个
-
如果字符串常量池中不存在"abc":
- 常量池中创建"abc"(1个)
- 堆中创建String对象(1个)
- 共2个对象
-
如果字符串常量池中已存在"abc":
- 只在堆中创建String对象(1个)
String s1 = "abc"; // 常量池
String s2 = new String("abc"); // 堆 + 常量池检查
System.out.println(s1 == s2); // false
System.out.println(s1 == s2.intern()); // true,intern()返回常量池引用
Q6: String常用的方法有哪些?
// 获取
length() // 长度
charAt(int index) // 指定位置字符
substring(int begin, int end) // 截取
// 判断
isEmpty() // 是否为空
equals(Object obj) // 内容比较
equalsIgnoreCase() // 忽略大小写比较
contains(CharSequence) // 是否包含
startsWith(String) // 是否以指定前缀开始
endsWith(String) // 是否以指定后缀结束
// 转换
toCharArray() // 转字符数组
toLowerCase() // 转小写
toUpperCase() // 转大写
trim() // 去首尾空格
// 查找
indexOf(int ch) // 字符首次出现位置
lastIndexOf(int ch) // 字符最后出现位置
// 替换
replace(char old, char new) // 替换字符
replaceAll(String regex, String replacement) // 正则替换
// 分割
split(String regex) // 按正则分割
// 拼接
concat(String str) // 拼接字符串
String.join(",", list) // 静态方法拼接
1.3 运算符
Q7: == 和 equals 的区别?
// == 比较规则
// 1. 基本类型:比较值
int a = 10, b = 10;
System.out.println(a == b); // true
// 2. 引用类型:比较内存地址
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false
// equals:默认比较地址,可重写比较内容
System.out.println(s1.equals(s2)); // true(String重写了equals)
// Object类默认equals
public boolean equals(Object obj) {
return (this == obj); // 默认就是==
}
// String类重写equals
public boolean equals(Object anObject) {
if (this == anObject) return true;
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
// 逐字符比较
// ...
}
return false;
}
答案要点:
| 类型 | == | equals |
|---|---|---|
| 基本类型 | 比较值 | 不适用 |
| 引用类型 | 比较地址 | 默认比较地址,可重写比较内容 |
Q8: hashCode()和equals()的关系?
规则:
- equals返回true,hashCode必须相同
- hashCode相同,equals不一定返回true(哈希冲突)
- 重写equals必须重写hashCode
// 为什么必须同时重写?
public class Person {
private String name;
private int age;
// 只重写equals
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof Person) {
Person p = (Person) obj;
return name.equals(p.name) && age == p.age;
}
return false;
}
// 必须重写hashCode
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
// HashSet场景
Set<Person> set = new HashSet<>();
Person p1 = new Person("Tom", 20);
Person p2 = new Person("Tom", 20);
set.add(p1);
set.add(p2);
// 如果不重写hashCode,p1和p2会有不同的hashCode
// 导致HashSet认为它们是不同的对象,都添加进去
1.4 流程控制
Q9: switch能作用于哪些数据类型?
// 支持的类型
byte, short, int, char // 基本类型
Byte, Short, Integer, Character // 包装类
String // JDK 7+
enum // 枚举
// 不支持
long, float, double, boolean
// 示例
String color = "red";
switch (color) {
case "red":
System.out.println("红色");
break;
case "blue":
System.out.println("蓝色");
break;
default:
System.out.println("未知");
}
Q10: break、continue、return的区别?
for (int i = 0; i < 10; i++) {
if (i == 3) break; // 跳出整个循环
if (i == 5) continue; // 跳过本次循环,继续下一次
if (i == 7) return; // 结束整个方法
System.out.println(i);
}
// 带标签的break
outer:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (i + j == 15) break outer; // 跳出外层循环
}
}
1.5 数组
Q11: 数组和ArrayList的区别?
| 特性 | 数组 | ArrayList |
|---|---|---|
| 长度 | 固定 | 动态扩容 |
| 类型 | 基本类型+引用类型 | 只能引用类型 |
| 性能 | 高 | 略低(封装开销) |
| 功能 | 基础 | 丰富(增删改查) |
// 数组
int[] arr = new int[10];
arr[0] = 1;
int len = arr.length; // 属性
// ArrayList
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
int len = list.size(); // 方法
Q12: 数组有哪些常见操作?
// 排序
int[] arr = {3, 1, 4, 1, 5};
Arrays.sort(arr); // [1, 1, 3, 4, 5]
// 二分查找(必须先排序)
int index = Arrays.binarySearch(arr, 3);
// 转字符串
String s = Arrays.toString(arr); // "[1, 1, 3, 4, 5]"
// 填充
Arrays.fill(arr, 0); // [0, 0, 0, 0, 0]
// 复制
int[] copy = Arrays.copyOf(arr, 10);
// 比较
boolean equals = Arrays.equals(arr1, arr2);
1.6 关键字
Q13: final、finally、finalize的区别?
| 关键字 | 作用 |
|---|---|
| final | 修饰类、方法、变量 |
| finally | 异常处理中的代码块 |
| finalize | Object类的方法(已废弃) |
// final
final class A {} // 类不能被继承
final void method() {} // 方法不能被重写
final int x = 10; // 变量不能被修改(常量)
// finally
try {
// 可能异常的代码
} catch (Exception e) {
// 异常处理
} finally {
// 无论是否异常都会执行(通常用于资源释放)
}
// finalize(已废弃,JDK 9+)
protected void finalize() throws Throwable {
super.finalize();
// 对象被回收前调用,不推荐使用
}
Q14: static关键字的作用?
public class Demo {
// 静态变量(类变量)
static int count = 0;
// 静态代码块(类加载时执行,只执行一次)
static {
System.out.println("类加载");
}
// 静态方法
public static void staticMethod() {
// 只能访问静态成员
// 不能使用this、super
}
// 静态内部类
static class InnerClass {}
}
// 调用方式
Demo.count = 10;
Demo.staticMethod();
static特点:
- 属于类,不属于对象
- 随类加载而加载
- 优先于对象存在
- 可以通过类名直接访问
Q15: this和super的区别?
| 关键字 | 作用 |
|---|---|
| this | 当前对象引用 |
| super | 父类对象引用 |
public class Child extends Parent {
private String name;
public Child(String name) {
super("parent"); // 调用父类构造器(必须第一行)
this.name = name; // 当前对象属性
}
@Override
public void method() {
super.method(); // 调用父类方法
this.method(); // 调用当前对象方法
}
}
第二部分:面向对象
2.1 三大特性
Q16: 面向对象的三大特性是什么?
| 特性 | 说明 |
|---|---|
| 封装 | 隐藏实现细节,提供公共访问方式 |
| 继承 | 子类继承父类的属性和方法 |
| 多态 | 同一行为的不同实现形式 |
// 封装
public class Person {
private String name; // 私有属性
public String getName() { // 公共访问方式
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 继承
public class Student extends Person {
private String school;
// 继承了Person的属性和方法
}
// 多态
Person p = new Student(); // 父类引用指向子类对象
p.getName(); // 调用子类的方法(如果重写了)
Q17: 访问修饰符有哪些?
| 修饰符 | 本类 | 同包 | 子类 | 其他 |
|---|---|---|---|---|
| public | ✓ | ✓ | ✓ | ✓ |
| protected | ✓ | ✓ | ✓ | ✗ |
| default | ✓ | ✓ | ✗ | ✗ |
| private | ✓ | ✗ | ✗ | ✗ |
2.2 接口与抽象类
Q18: 接口和抽象类的区别?
| 特性 | 接口 | 抽象类 |
|---|---|---|
| 关键字 | interface | abstract class |
| 继承 | 多实现 | 单继承 |
| 成员变量 | 只能public static final | 任意 |
| 构造方法 | 无 | 有 |
| 方法 | JDK8前全抽象,之后可有默认方法 | 可抽象可具体 |
| 设计理念 | "像…"的能力 | "是…"的关系 |
// 接口
interface Flyable {
int MAX_HEIGHT = 10000; // 默认public static final
void fly(); // 默认public abstract
// JDK 8: 默认方法
default void defaultMethod() {
System.out.println("默认方法");
}
// JDK 8: 静态方法
static void staticMethod() {
System.out.println("静态方法");
}
// JDK 9: 私有方法
private void privateMethod() {
System.out.println("私有方法");
}
}
// 抽象类
abstract class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public abstract void eat(); // 抽象方法
public void sleep() { // 具体方法
System.out.println("睡觉");
}
}
Q19: 什么时候用接口,什么时候用抽象类?
使用接口:
- 需要多继承
- 定义行为规范,不关心实现
- 不同类有共同行为但无继承关系
使用抽象类:
- 需要共享代码
- 需要成员变量
- 需要非public成员
- 需要构造方法
// 接口:行为能力
interface Flyable { void fly(); }
interface Swimable { void swim(); }
// 抽象类:共同特征
abstract class Animal {
protected String name;
public abstract void eat();
}
// 组合使用
class Duck extends Animal implements Flyable, Swimable {
@Override
public void eat() { }
@Override
public void fly() { }
@Override
public void swim() { }
}
2.3 重载与重写
Q20: 重载和重写的区别?
| 特性 | 重载(Overload) | 重写(Override) |
|---|---|---|
| 位置 | 同一类中 | 子父类之间 |
| 方法签名 | 方法名相同,参数列表不同 | 完全相同 |
| 返回类型 | 无关 | 相同或协变 |
| 访问权限 | 无关 | 不能更严格 |
| 异常 | 无关 | 不能更宽泛 |
// 重载:同一类中,参数列表不同
public class Calculator {
public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; }
public int add(int a, int b, int c) { return a + b + c; }
}
// 重写:子类重写父类方法
class Parent {
public void show() { System.out.println("Parent"); }
}
class Child extends Parent {
@Override
public void show() { System.out.println("Child"); }
}
Q21: 重写的规则有哪些?
class Parent {
protected Number method() throws IOException { return 1; }
}
class Child extends Parent {
// ✓ 正确重写
@Override
public Integer method() throws FileNotFoundException {
return 2;
}
// ✗ 编译错误
// private Number method() // 访问权限更严格
// public Object method() // 返回类型不兼容
// public Number method() throws Exception // 异常更宽泛
}
重写规则:
- 方法名、参数列表必须相同
- 返回类型相同或为子类(协变返回类型)
- 访问权限不能更严格
- 抛出异常不能更宽泛
- final方法不能重写
- static方法不能重写(可隐藏)
2.4 构造方法
Q22: 构造方法的特点?
public class Person {
private String name;
private int age;
// 无参构造
public Person() {
this("Unknown", 0); // 调用其他构造器(必须第一行)
}
// 有参构造
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 私有构造(单例模式)
private Person(String name) {
this.name = name;
}
}
特点:
- 名字与类名相同
- 没有返回类型(不是void)
- 可以重载
- 不能被继承
- 默认提供无参构造(如果没写任何构造器)
Q23: 子类构造方法如何调用父类构造方法?
class Parent {
private String name;
public Parent(String name) {
this.name = name;
}
}
class Child extends Parent {
private int age;
public Child(String name, int age) {
super(name); // 必须第一行,调用父类构造
this.age = age;
}
public Child(int age) {
super("default"); // 显式调用
this.age = age;
}
}
规则:
- 子类构造器必须调用父类构造器(显式或隐式)
- super()必须在第一行
- 不写super()时,默认调用父类无参构造
- 父类没有无参构造时,必须显式调用
2.5 内部类
Q24: 内部类的分类?
public class Outer {
private int x = 10;
// 成员内部类
class Inner {
public void method() {
System.out.println(x); // 访问外部类成员
}
}
// 静态内部类
static class StaticInner {
public void method() {
// System.out.println(x); // 不能访问非静态成员
System.out.println("静态内部类");
}
}
public void outerMethod() {
// 局部内部类
class LocalInner {
public void method() {
System.out.println(x);
}
}
LocalInner local = new LocalInner();
local.method();
}
public void test() {
// 匿名内部类
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类");
}
};
new Thread(r).start();
// Lambda简化
new Thread(() -> System.out.println("Lambda")).start();
}
}
内部类特点:
| 类型 | 特点 |
|---|---|
| 成员内部类 | 可以访问外部类所有成员,依赖外部类实例 |
| 静态内部类 | 只能访问外部类静态成员,不依赖外部类实例 |
| 局部内部类 | 定义在方法内,作用域限定于方法 |
| 匿名内部类 | 没有名字,只能实例化一次 |
第三部分:Java集合
3.1 集合框架概述
Q25: Java集合框架的结构?
Collection(单列集合)
├── List(有序、可重复)
│ ├── ArrayList
│ ├── LinkedList
│ └── Vector
│ └── Stack
├── Set(无序、不可重复)
│ ├── HashSet
│ │ └── LinkedHashSet
│ └── TreeSet
└── Queue(队列)
├── LinkedList
├── PriorityQueue
└── ArrayDeque
Map(双列集合)
├── HashMap
│ └── LinkedHashMap
├── TreeMap
├── Hashtable
│ └── Properties
└── ConcurrentHashMap
3.2 List集合
Q26: ArrayList和LinkedList的区别?
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 动态数组 | 双向链表 |
| 随机访问 | O(1) | O(n) |
| 插入删除 | O(n) | O(1)(已知位置) |
| 内存占用 | 连续空间 | 节点开销大 |
| 扩容 | 1.5倍 | 无需扩容 |
| 适用场景 | 查询多 | 增删多 |
// ArrayList扩容源码
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍
// ...
}
// LinkedList节点
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
Q27: Vector和ArrayList的区别?
| 特性 | ArrayList | Vector |
|---|---|---|
| 线程安全 | 不安全 | 安全(synchronized) |
| 性能 | 高 | 低 |
| 扩容 | 1.5倍 | 2倍 |
| 使用场景 | 单线程 | 多线程(已过时,用CopyOnWriteArrayList) |
Q28: List的遍历方式?
List<String> list = Arrays.asList("a", "b", "c");
// 1. for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 2. 增强for循环
for (String s : list) {
System.out.println(s);
}
// 3. Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
// 4. forEach + Lambda
list.forEach(System.out::println);
// 5. Stream
list.stream().forEach(System.out::println);
// 6. ListIterator(双向遍历)
ListIterator<String> lit = list.listIterator();
while (lit.hasNext()) {
System.out.println(lit.next());
}
while (lit.hasPrevious()) {
System.out.println(lit.previous());
}
Q29: 遍历时删除元素的正确方式?
List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
// ✗ 错误:ConcurrentModificationException
for (Integer i : list) {
if (i == 3) list.remove(i);
}
// ✗ 错误:跳过元素
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == 3) list.remove(i);
}
// ✓ 正确:Iterator
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
if (it.next() == 3) it.remove();
}
// ✓ 正确:倒序遍历
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i) == 3) list.remove(i);
}
// ✓ 正确:removeIf(JDK 8+)
list.removeIf(i -> i == 3);
// ✓ 正确:CopyOnWriteArrayList
CopyOnWriteArrayList<Integer> copyList = new CopyOnWriteArrayList<>(list);
for (Integer i : copyList) {
if (i == 3) copyList.remove(i);
}
3.3 Set集合
Q30: HashSet、LinkedHashSet、TreeSet的区别?
| 特性 | HashSet | LinkedHashSet | TreeSet |
|---|---|---|---|
| 底层 | 哈希表 | 哈希表+链表 | 红黑树 |
| 有序性 | 无序 | 插入顺序 | 排序顺序 |
| 性能 | O(1) | O(1) | O(log n) |
| null | 允许 | 允许 | 不允许 |
// HashSet
Set<String> hashSet = new HashSet<>();
hashSet.add("c");
hashSet.add("a");
hashSet.add("b");
System.out.println(hashSet); // [a, b, c] 顺序不确定
// LinkedHashSet(保持插入顺序)
Set<String> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add("c");
linkedHashSet.add("a");
linkedHashSet.add("b");
System.out.println(linkedHashSet); // [c, a, b]
// TreeSet(排序)
Set<String> treeSet = new TreeSet<>();
treeSet.add("c");
treeSet.add("a");
treeSet.add("b");
System.out.println(treeSet); // [a, b, c]
Q31: HashSet如何保证元素唯一?
// 添加元素时的判断逻辑
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
// HashMap的put方法
public V put(K key, V value) {
// 1. 计算hashCode
int hash = key.hashCode();
// 2. 计算索引位置
int index = hash % table.length;
// 3. 遍历该位置的链表
for (Node<K,V> node : table[index]) {
// 4. 比较hashCode和equals
if (node.hash == hash &&
(node.key == key || key.equals(node.key))) {
return node.value; // 已存在,不添加
}
}
// 5. 不存在,添加新节点
addNode(hash, key, value, index);
return null;
}
结论:先比较hashCode,再比较equals。都相同才认为是重复元素。
Q32: TreeSet的排序方式?
// 1. 自然排序(实现Comparable)
class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public int compareTo(Person o) {
return this.age - o.age; // 按年龄排序
}
}
Set<Person> set1 = new TreeSet<>();
set1.add(new Person("Tom", 20));
set1.add(new Person("Jerry", 18));
// 2. 定制排序(Comparator)
Set<Person> set2 = new TreeSet<>((p1, p2) -> p1.getName().compareTo(p2.getName()));
// 或
Set<Person> set3 = new TreeSet<>(Comparator.comparing(Person::getName));
// 3. 多字段排序
Set<Person> set4 = new TreeSet<>(
Comparator.comparing(Person::getAge)
.thenComparing(Person::getName)
);
3.4 Map集合
Q33: HashMap的底层结构?
JDK 1.7:数组 + 链表
JDK 1.8:数组 + 链表 + 红黑树
// HashMap的关键属性
public class HashMap<K,V> {
// 默认初始容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认负载因子 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 链表转红黑树阈值
static final int TREEIFY_THRESHOLD = 8;
// 红黑树转链表阈值
static final int UNTREEIFY_THRESHOLD = 6;
// 转红黑树时数组最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
// 哈希桶数组
transient Node<K,V>[] table;
// 当前元素个数
transient int size;
// 扩容阈值 = 容量 * 负载因子
int threshold;
// 负载因子
final float loadFactor;
}
Q34: HashMap的扩容机制?
// 扩容条件:size > threshold(容量 * 负载因子)
// 扩容大小:原来的2倍
void resize() {
Node<K,V>[] oldTab = table;
int oldCap = oldTab.length;
int newCap = oldCap << 1; // 2倍扩容
// 创建新数组
Node<K,V>[] newTab = new Node[newCap];
// 重新计算位置并迁移元素
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
if (e.next == null) {
// 单个节点,直接重新计算位置
newTab[e.hash & (newCap - 1)] = e;
} else if (e instanceof TreeNode) {
// 红黑树拆分
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
} else {
// 链表拆分:原位置或原位置+oldCap
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
do {
if ((e.hash & oldCap) == 0) {
// 原位置
if (loTail == null) loHead = e;
else loTail.next = e;
loTail = e;
} else {
// 原位置 + oldCap
if (hiTail == null) hiHead = e;
else hiTail.next = e;
hiTail = e;
}
} while ((e = e.next) != null);
}
}
}
}
Q35: HashMap为什么是线程不安全的?
// 1. JDK 1.7:扩容时链表死循环
// 多线程扩容时,链表迁移可能导致环形链表
// 2. JDK 1.8:数据丢失
// 多线程put时,同时判断同一位置为空,后者覆盖前者
// 3. size统计不准确
// ++modCount; ++size 不是原子操作
// 解决方案
// 方案1:Collections.synchronizedMap
Map<String, String> map1 = Collections.synchronizedMap(new HashMap<>());
// 方案2:ConcurrentHashMap(推荐)
Map<String, String> map2 = new ConcurrentHashMap<>();
// 方案3:Hashtable(不推荐,性能差)
Map<String, String> map3 = new Hashtable<>();
Q36: ConcurrentHashMap的实现原理?
JDK 1.7:
- 分段锁(Segment数组)
- 每个Segment继承ReentrantLock
- 并发度 = Segment个数
JDK 1.8:
- 数组 + 链表 + 红黑树
- CAS + synchronized(锁单个桶)
- 锁粒度更细,性能更高
// JDK 1.8 put方法核心逻辑
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 1. 计算hash
int hash = spread(key.hashCode());
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 2. 数组为空,初始化
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 3. 目标桶为空,CAS插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))
break;
}
// 4. 正在扩容,帮忙迁移
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 5. 插入链表或红黑树
else {
synchronized (f) {
// 遍历链表/红黑树,插入或更新
// ...
}
}
}
// 6. 检查是否需要树化
addCount(1L, binCount);
return null;
}
Q37: HashMap和Hashtable的区别?
| 特性 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | 不安全 | 安全(synchronized) |
| null键值 | 允许 | 不允许 |
| 继承 | AbstractMap | Dictionary |
| 初始容量 | 16 | 11 |
| 扩容 | 2倍 | 2倍+1 |
| 哈希计算 | 异或高低位 | 直接取模 |
| 推荐 | ✓ | ✗(用ConcurrentHashMap) |
3.5 集合工具类
Q38: Collections常用方法?
List<Integer> list = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5));
// 排序
Collections.sort(list); // [1, 1, 3, 4, 5]
Collections.sort(list, Comparator.reverseOrder()); // 降序
// 二分查找(必须先排序)
int index = Collections.binarySearch(list, 3);
// 最大最小
Integer max = Collections.max(list);
Integer min = Collections.min(list);
// 反转
Collections.reverse(list);
// 随机打乱
Collections.shuffle(list);
// 填充
Collections.fill(list, 0);
// 复制
List<Integer> copy = new ArrayList<>(Collections.nCopies(5, 0));
Collections.copy(copy, list);
// 不可变集合
List<Integer> unmodifiable = Collections.unmodifiableList(list);
// 同步集合
List<Integer> synchronizedList = Collections.synchronizedList(list);
// 单元素集合
List<Integer> singleton = Collections.singletonList(1);
第四部分:Java多线程与并发
4.1 线程基础
Q39: 创建线程的方式?
// 方式1:继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
new MyThread().start();
// 方式2:实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
new Thread(new MyRunnable()).start();
// 方式3:实现Callable接口(有返回值)
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
FutureTask<String> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
String result = future.get(); // 阻塞获取结果
// 方式4:线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> System.out.println("ThreadPool running"));
executor.shutdown();
// Lambda简化
new Thread(() -> System.out.println("Lambda")).start();
Q40: 线程的生命周期?
┌────────────────────────────────────────┐
│ │
▼ │
┌─────────┐ start() ┌──────────┐ │
│ NEW │ ────────> │ RUNNABLE │ │
└─────────┘ └──────────┘ │
│ │ │ │
┌────────────────┘ │ └──────────┐ │
│ │ │ │
▼ ▼ ▼ │
┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ BLOCKED │ │ WAITING │ │TIMED_ │ │
│(等待锁) │ │(无限等待)│ │WAITING │ │
└──────────┘ └──────────┘ │(有限等待) │ │
│ │ └──────────┘ │
│ │ │ │
└────────────────────┴──────────────┘ │
│ │
▼ │
┌─────────────┐ │
│ TERMINATED │◄─────────────┘
└─────────────┘
// 状态转换示例
Thread thread = new Thread(() -> {
try {
Thread.sleep(1000); // TIMED_WAITING
synchronized (Object.class) {
Object.class.wait(); // WAITING
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
Q41: sleep()和wait()的区别?
| 特性 | sleep() | wait() |
|---|---|---|
| 所属类 | Thread | Object |
| 是否释放锁 | 否 | 是 |
| 使用位置 | 任意 | 同步代码块 |
| 唤醒方式 | 超时自动唤醒 | notify/notifyAll或超时 |
| 用途 | 暂停执行 | 线程通信 |
// sleep:不释放锁
synchronized (lock) {
Thread.sleep(1000); // 休眠1秒,但持有锁
// 其他线程无法获取锁
}
// wait:释放锁
synchronized (lock) {
lock.wait(); // 释放锁并等待
// 其他线程可以获取锁
// 被notify唤醒后,重新获取锁继续执行
}
Q42: start()和run()的区别?
Thread thread = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
thread.run(); // 主线程执行,输出:main
thread.start(); // 新线程执行,输出:Thread-0
// start()源码
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
nativeCreate(this, stackSize, daemon); // 调用本地方法创建新线程
}
// start()会调用run(),但在新线程中执行
4.2 线程同步
Q43: synchronized的使用方式?
public class SynchronizedDemo {
private final Object lock = new Object();
private static int count = 0;
// 1. 同步实例方法(锁当前实例this)
public synchronized void instanceMethod() {
// 临界区
}
// 2. 同步静态方法(锁Class对象)
public static synchronized void staticMethod() {
// 临界区
}
// 3. 同步代码块(锁指定对象)
public void blockMethod() {
synchronized (lock) {
// 临界区
}
}
// 4. 同步代码块(锁当前实例)
public void blockThis() {
synchronized (this) {
// 等同于同步实例方法
}
}
// 5. 同步代码块(锁Class对象)
public void blockClass() {
synchronized (SynchronizedDemo.class) {
// 等同于同步静态方法
}
}
}
Q44: synchronized的锁升级过程?
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
| 锁状态 | 适用场景 | 性能 |
|---|---|---|
| 无锁 | 无竞争 | 最高 |
| 偏向锁 | 单线程重入 | 高 |
| 轻量级锁 | 交替执行 | 中 |
| 重量级锁 | 竞争激烈 | 低 |
// 对象头中的Mark Word结构(64位JVM)
// | 锁状态 | 存储内容 |
// |--------|----------|
// | 无锁 | hashCode, age, 01 |
// | 偏向锁 | 线程ID, epoch, age, 01 |
// | 轻量级锁 | 指向栈中锁记录的指针, 00 |
// | 重量级锁 | 指向互斥量的指针, 10 |
// 锁升级过程
// 1. 偏向锁:第一个线程访问时,在对象头记录线程ID
// 2. 轻量级锁:其他线程竞争时,CAS尝试获取,失败则自旋
// 3. 重量级锁:自旋超过阈值,升级为重量级锁,线程阻塞
Q45: synchronized和Lock的区别?
| 特性 | synchronized | Lock |
|---|---|---|
| 层次 | JVM关键字 | Java接口 |
| 获取锁 | 自动 | 手动lock() |
| 释放锁 | 自动 | 手动unlock() |
| 可中断 | 否 | lockInterruptibly() |
| 公平性 | 非公平 | 可选公平/非公平 |
| 条件变量 | 单一 | 多个Condition |
| 尝试获取锁 | 否 | tryLock() |
| 性能 | JDK6后优化,差距不大 | 高竞争时更好 |
// Lock标准用法
Lock lock = new ReentrantLock();
try {
lock.lock(); // 获取锁
// 临界区
} finally {
lock.unlock(); // 必须释放锁
}
// 尝试获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 临界区
} finally {
lock.unlock();
}
} else {
// 获取失败
}
// 可中断获取锁
try {
lock.lockInterruptibly();
// 临界区
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
// 公平锁
Lock fairLock = new ReentrantLock(true);
// Condition(条件变量)
ReentrantLock reLock = new ReentrantLock();
Condition condition = reLock.newCondition();
// await() / signal() / signalAll()
4.3 volatile关键字
Q46: volatile的作用?
public class VolatileDemo {
// 1. 可见性
private volatile boolean running = true;
public void stop() {
running = false; // 对其他线程立即可见
}
public void doWork() {
while (running) {
// 工作内容
}
}
// 2. 禁止指令重排序
private volatile Singleton instance;
public Singleton getInstance() {
if (instance == null) { // 1
synchronized (VolatileDemo.class) {
if (instance == null) {
instance = new Singleton(); // 2
// 不使用volatile可能重排序:
// memory = allocate();
// instance = memory; // 先赋值
// ctorInstance(memory); // 后初始化
// 其他线程可能看到未初始化完成的对象
}
}
}
return instance;
}
}
Q47: volatile为什么不保证原子性?
public class VolatileAtomic {
private volatile int count = 0;
public void increment() {
count++; // 非原子操作
// 实际是3步:
// 1. 读取count
// 2. count + 1
// 3. 写入count
}
// 多线程测试
public static void main(String[] args) throws InterruptedException {
VolatileAtomic demo = new VolatileAtomic();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
demo.increment();
}
});
}
for (Thread t : threads) t.start();
for (Thread t : threads) t.join();
System.out.println(demo.count); // 结果 < 10000
}
}
解决方案:
// 1. synchronized
public synchronized void increment() { count++; }
// 2. AtomicInteger
private AtomicInteger count = new AtomicInteger(0);
public void increment() { count.incrementAndGet(); }
// 3. ReentrantLock
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try { count++; } finally { lock.unlock(); }
}
4.4 CAS与原子类
Q48: CAS是什么?
Compare And Swap(比较并交换):乐观锁实现
// CAS操作原理(伪代码)
public boolean compareAndSwap(int expected, int newValue) {
if (this.value == expected) { // 比较
this.value = newValue; // 交换
return true;
}
return false;
}
// AtomicInteger源码
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// Unsafe类中的CAS
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset); // 读取当前值
} while (!compareAndSwapInt(o, offset, v, v + delta)); // CAS失败则重试
return v;
}
Q49: CAS的ABA问题?
// ABA问题场景
// 线程1: 读取A → 准备CAS(A, B)
// 线程2: A → C → A
// 线程1: CAS成功,但实际上中间被修改过
// 解决方案:版本号/时间戳
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(1, 0);
// 更新时携带版本号
int stamp = ref.getStamp();
ref.compareAndSet(1, 2, stamp, stamp + 1);
// AtomicMarkableReference(boolean标记)
AtomicMarkableReference<Integer> markRef = new AtomicMarkableReference<>(1, false);
Q50: JUC原子类有哪些?
Atomic类
├── 基本类型
│ ├── AtomicInteger
│ ├── AtomicLong
│ └── AtomicBoolean
├── 数组类型
│ ├── AtomicIntegerArray
│ ├── AtomicLongArray
│ └── AtomicReferenceArray
├── 引用类型
│ ├── AtomicReference
│ ├── AtomicStampedReference
│ └── AtomicMarkableReference
└── 字段更新器
├── AtomicIntegerFieldUpdater
├── AtomicLongFieldUpdater
└── AtomicReferenceFieldUpdater
// 常用方法
AtomicInteger atomic = new AtomicInteger(0);
atomic.get(); // 获取值
atomic.set(10); // 设置值
atomic.getAndSet(10); // 获取并设置
atomic.compareAndSet(0, 10); // CAS
atomic.getAndIncrement(); // 获取并自增
atomic.incrementAndGet(); // 自增并获取
atomic.getAndAdd(5); // 获取并加
atomic.addAndGet(5); // 加并获取
atomic.updateAndGet(x -> x * 2); // 原子更新
// LongAdder(高并发累加)
LongAdder adder = new LongAdder();
adder.increment();
adder.add(10);
long sum = adder.sum(); // 最终一致性
4.5 AQS
Q51: AQS是什么?
AbstractQueuedSynchronizer:抽象队列同步器,JUC核心基础
// AQS核心设计
public abstract class AbstractQueuedSynchronizer {
// 同步状态(volatile)
private volatile int state;
// CLH队列头节点
private transient volatile Node head;
// CLH队列尾节点
private transient volatile Node tail;
// 队列节点
static final class Node {
volatile Node prev;
volatile Node next;
volatile Thread thread; // 等待的线程
volatile int waitStatus; // 等待状态
}
}
核心原理:
- state:同步状态,0表示未占用,>0表示占用
- CLH队列:双向链表,存储等待线程
- 独占模式:只有一个线程能获取锁(ReentrantLock)
- 共享模式:多个线程可同时获取锁(CountDownLatch)
Q52: AQS的应用?
| 类 | 模式 | 说明 |
|---|---|---|
| ReentrantLock | 独占 | 可重入锁 |
| ReentrantReadWriteLock | 独占+共享 | 读写锁 |
| CountDownLatch | 共享 | 倒计时器 |
| Semaphore | 共享 | 信号量 |
| CyclicBarrier | 共享 | 循环栅栏 |
// 简单实现独占锁
class SimpleLock extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setState(0);
setExclusiveOwnerThread(null);
return true;
}
public void lock() { acquire(1); }
public void unlock() { release(1); }
}
4.6 线程池
Q53: 线程池核心参数?
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
执行流程:
提交任务
│
▼
核心线程数未满? ───是──→ 创建核心线程执行
│
否
▼
工作队列未满? ───是──→ 加入队列等待
│
否
▼
最大线程数未满? ───是──→ 创建非核心线程执行
│
否
▼
执行拒绝策略
Q54: 线程池的工作队列?
| 队列 | 特点 | 适用场景 |
|---|---|---|
| ArrayBlockingQueue | 有界数组队列 | 防止资源耗尽 |
| LinkedBlockingQueue | 可选有界链表队列 | 默认无界(危险) |
| SynchronousQueue | 不存储元素 | 直接传递,高吞吐 |
| PriorityBlockingQueue | 无界优先队列 | 按优先级执行 |
| DelayQueue | 无界延迟队列 | 定时任务 |
Q55: 线程池的拒绝策略?
| 策略 | 行为 |
|---|---|
| AbortPolicy(默认) | 抛出RejectedExecutionException |
| CallerRunsPolicy | 由调用线程执行任务 |
| DiscardPolicy | 直接丢弃,不抛异常 |
| DiscardOldestPolicy | 丢弃队列最老的任务,重新提交 |
// 自定义拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, 10, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 或自定义
executor.setRejectedExecutionHandler((r, e) -> {
// 记录日志
log.warn("任务被拒绝: " + r.toString());
// 存储到数据库
saveToDatabase(r);
});
Q56: 如何合理配置线程池?
// CPU密集型
int cpuCount = Runtime.getRuntime().availableProcessors();
int poolSize = cpuCount + 1;
// IO密集型
int poolSize = cpuCount * 2;
// 混合型
int poolSize = cpuCount * (1 + 等待时间/计算时间);
// 示例配置
ThreadPoolExecutor executor = new ThreadPoolExecutor(
8, // 核心线程 = CPU核心数
16, // 最大线程
60L, TimeUnit.SECONDS, // 空闲时间
new ArrayBlockingQueue<>(200), // 有界队列
new ThreadFactoryBuilder().setNameFormat("worker-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 监控线程池
executor.getActiveCount(); // 活动线程数
executor.getQueue().size(); // 队列任务数
executor.getCompletedTaskCount(); // 已完成任务数
executor.getPoolSize(); // 当前线程数
Q57: Executors创建线程池的问题?
// 问题1:FixedThreadPool/SingleThreadPool
// 队列无界,可能OOM
ExecutorService fixed = Executors.newFixedThreadPool(10);
// 使用LinkedBlockingQueue(无界)
// 问题2:CachedThreadPool
// 最大线程数Integer.MAX_VALUE,可能OOM
ExecutorService cached = Executors.newCachedThreadPool();
// 使用SynchronousQueue,线程数无限
// 问题3:ScheduledThreadPool
// 最大线程数Integer.MAX_VALUE
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(10);
// 正确做法:手动创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueCapacity),
new CustomThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
4.7 并发工具类
Q58: CountDownLatch的使用?
// 场景:等待多个线程完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
// 执行任务
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 完成");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 计数减1
}
}).start();
}
latch.await(); // 阻塞等待计数归零
System.out.println("所有任务完成");
// 超时等待
boolean completed = latch.await(5, TimeUnit.SECONDS);
Q59: CyclicBarrier的使用?
// 场景:多个线程互相等待,一起出发
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程就绪,开始执行");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 就绪");
barrier.await(); // 等待其他线程
System.out.println(Thread.currentThread().getName() + " 执行");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
// 可重用
barrier.reset();
Q60: Semaphore的使用?
// 场景:限流,控制并发数
Semaphore semaphore = new Semaphore(3); // 同时最多3个线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println(Thread.currentThread().getName() + " 执行");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}).start();
}
// 尝试获取
boolean acquired = semaphore.tryAcquire(1, TimeUnit.SECONDS);
Q61: CountDownLatch vs CyclicBarrier?
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 计数方向 | 递减 | 递增 |
| 可重用 | 否 | 是 |
| 触发条件 | 计数归零 | 到达指定数量 |
| 使用场景 | 等待一组线程完成 | 一组线程互相等待 |
4.8 ThreadLocal
Q62: ThreadLocal的原理?
// 每个Thread对象持有ThreadLocalMap
public class Thread {
ThreadLocal.ThreadLocalMap threadLocals;
}
// ThreadLocalMap是ThreadLocal的内部类
// Entry的key是ThreadLocal对象,value是线程本地值
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
原理图解:
Thread
└── ThreadLocalMap
├── Entry[key=ThreadLocal1, value=Value1]
├── Entry[key=ThreadLocal2, value=Value2]
└── Entry[key=ThreadLocal3, value=Value3]
Q63: ThreadLocal的使用场景?
// 场景1:数据库连接
private static ThreadLocal<Connection> connectionHolder =
ThreadLocal.withInitial(() -> null);
public static Connection getConnection() {
Connection conn = connectionHolder.get();
if (conn == null) {
conn = DriverManager.getConnection(url, user, password);
connectionHolder.set(conn);
}
return conn;
}
// 场景2:日期格式化
private static ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormat.get().format(date);
}
// 场景3:用户上下文
public class UserContext {
private static ThreadLocal<User> userHolder = new ThreadLocal<>();
public static void setUser(User user) {
userHolder.set(user);
}
public static User getUser() {
return userHolder.get();
}
public static void clear() {
userHolder.remove(); // 重要:防止内存泄漏
}
}
Q64: ThreadLocal的内存泄漏问题?
// 问题:Entry的key是弱引用,value是强引用
// key被回收后,value无法被访问,但不会被回收
// 解决方案:使用后调用remove()
try {
threadLocal.set(value);
// 业务逻辑
} finally {
threadLocal.remove(); // 必须清理
}
// ThreadLocalMap也会在get/set时清理过期Entry
// 但最好手动remove,避免等待
第五部分:JVM虚拟机
5.1 JVM内存结构
Q65: JVM运行时数据区?
┌─────────────────────────────────────────────────────┐
│ JVM运行时数据区 │
├─────────────┬─────────────┬─────────────────────────┤
│ 线程私有 │ 线程私有 │ 线程共享 │
├─────────────┼─────────────┼───────────┬─────────────┤
│ 程序计数器 │ 虚拟机栈 │ 堆 │ 方法区 │
│ (PC) │ (Stack) │ (Heap) │ (Metaspace) │
├─────────────┼─────────────┼───────────┴─────────────┤
│ 本地方法栈 │ │ │
│ (Native) │ │ │
└─────────────┴─────────────┴─────────────────────────┘
| 区域 | 作用 | 异常 |
|---|---|---|
| 程序计数器 | 当前执行的字节码行号 | 无 |
| 虚拟机栈 | 方法调用、局部变量 | StackOverflowError、OOM |
| 本地方法栈 | Native方法服务 | StackOverflowError、OOM |
| 堆 | 对象实例 | OutOfMemoryError |
| 方法区 | 类信息、常量、静态变量 | OutOfMemoryError |
Q66: 堆内存结构?
┌─────────────────────────────────────────────┐
│ 堆(Heap) │
├───────────────────┬─────────────────────────┤
│ 新生代 │ 老年代 │
│ (Young Generation)│ (Old Generation) │
├─────────┬─────────┤ │
│ Eden │ Survivor│ │
│ │ S0 S1 │ │
│ 8 │ 1 1 │ │
│ (80%) │ (10%) │ (2/3) │
└─────────┴─────────┴─────────────────────────┘
新生代占1/3,老年代占2/3
// JVM参数
-Xms1024m // 堆初始大小
-Xmx1024m // 堆最大大小
-Xmn512m // 新生代大小
-XX:SurvivorRatio=8 // Eden:Survivor = 8:1:1
-XX:NewRatio=2 // 新生代:老年代 = 1:2
Q67: 方法区的演变?
| JDK版本 | 方法区实现 |
|---|---|
| JDK 7及之前 | 永久代(PermGen),JVM内存 |
| JDK 8及之后 | 元空间(Metaspace),本地内存 |
// JDK 8参数
-XX:MetaspaceSize=128m // 初始大小
-XX:MaxMetaspaceSize=256m // 最大大小
// 运行时常量池
// JDK 7:从方法区移到堆
// JDK 8:在堆中
// 字符串常量池
// JDK 7:从方法区移到堆
String.intern(); // 如果池中没有,放入池中并返回引用
5.2 垃圾回收
Q68: 如何判断对象可回收?
// 1. 引用计数法(Python使用)
// 问题:循环引用无法回收
// 2. 可达性分析(Java使用)
// 从GC Roots开始向下搜索,不可达的对象可回收
// GC Roots包括:
// - 虚拟机栈中的引用
// - 方法区静态属性引用
// - 方法区常量引用
// - 本地方法栈JNI引用
// - 同步锁持有的对象
// - JVM内部引用(基本类型Class对象等)
Q69: Java的引用类型?
| 引用类型 | 回收时机 | 用途 |
|---|---|---|
| 强引用 | 永不回收(除非不可达) | 普通对象 |
| 软引用 | 内存不足时 | 缓存 |
| 弱引用 | 下次GC | WeakHashMap |
| 虚引用 | 随时可能 | 跟踪回收 |
// 强引用
Object strong = new Object();
// 软引用
SoftReference<Object> soft = new SoftReference<>(new Object());
Object obj = soft.get(); // 获取对象,可能为null
// 弱引用
WeakReference<Object> weak = new WeakReference<>(new Object());
Object obj = weak.get(); // 获取对象,可能为null
// 虚引用
PhantomReference<Object> phantom = new PhantomReference<>(
new Object(), new ReferenceQueue<>());
Object obj = phantom.get(); // 总是返回null
Q70: 垃圾回收算法?
| 算法 | 原理 | 优点 | 缺点 | 适用 |
|---|---|---|---|---|
| 标记-清除 | 标记后清除 | 简单 | 内存碎片 | 老年代 |
| 复制算法 | 分两块,存活复制 | 无碎片 | 内存利用率低 | 新生代 |
| 标记-整理 | 标记后移动到一端 | 无碎片 | 效率低 | 老年代 |
| 分代收集 | 新老年代不同算法 | 综合 | 复杂 | 主流 |
// 新生代:复制算法
// Eden + Survivor0 + Survivor1
// 新对象在Eden分配
// GC时,存活对象复制到另一Survivor
// 老年代:标记-清除或标记-整理
// 大对象直接进入老年代
// 长期存活对象进入老年代
Q71: 常见垃圾收集器?
| 收集器 | 类型 | 算法 | 特点 |
|---|---|---|---|
| Serial | 新生代 | 复制 | 单线程,简单高效 |
| ParNew | 新生代 | 复制 | 多线程Serial |
| Parallel Scavenge | 新生代 | 复制 | 吞吐量优先 |
| Serial Old | 老年代 | 标记-整理 | 单线程 |
| Parallel Old | 老年代 | 标记-整理 | 多线程 |
| CMS | 老年代 | 标记-清除 | 低停顿 |
| G1 | 新老年代 | 复制+标记-整理 | 分区收集 |
| ZGC | 全堆 | 复制 | 低延迟(JDK11+) |
// 常用组合
// Serial + Serial Old:单线程应用
// ParNew + CMS:低停顿
// Parallel Scavenge + Parallel Old:吞吐量
// G1:大内存、可预测停顿
// 参数
-XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
-XX:+UseZGC
Q72: CMS收集器的工作流程?
1. 初始标记(STW):标记GC Roots直接关联的对象
2. 并发标记:从GC Roots遍历整个对象图
3. 重新标记(STW):修正并发标记期间的变动
4. 并发清除:清除标记的对象
特点:
- 并发收集,低停顿
- 标记-清除,有碎片
- CPU敏感
- 浮动垃圾问题
Q73: G1收集器的特点?
┌────┬────┬────┬────┐
│ E │ S │ │ │ E = Eden
├────┼────┼────┼────┤ S = Survivor
│ │ O │ O │ O │ O = Old
├────┼────┼────┼────┤ H = Humongous
│ │ H │ │ │
├────┼────┼────┼────┤
│ │ │ │ │
└────┴────┴────┴────┘
Region分区
特点:
- 分区收集
- 可预测停顿
- 无内存碎片
- 大对象特殊处理
模式:
- Young GC:新生代收集
- Mixed GC:新生代+部分老年代
- Full GC:全面收集(应避免)
5.3 类加载
Q74: 类加载过程?
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
│ │ │ │ │
│ │ │ │ └─ 执行<clinit>,静态变量赋值、静态代码块
│ │ │ └──────── 符号引用 → 直接引用
│ │ └─────────────── 为静态变量分配内存,设置默认值
│ └────────────────────── 校验字节码正确性
└───────────────────────────── 读取class文件,创建Class对象
public class ClassInitDemo {
private static int value = 123; // 准备阶段:value=0,初始化阶段:value=123
static {
System.out.println("静态代码块");
}
// <clinit>方法按顺序执行静态变量赋值和静态代码块
}
Q75: 类加载器有哪些?
Bootstrap ClassLoader(启动类加载器)
↑ 加载JAVA_HOME/lib/*.jar
│
Extension ClassLoader(扩展类加载器)
↑ 加载JAVA_HOME/lib/ext/*.jar
│
Application ClassLoader(应用类加载器)
↑ 加载classpath下的类
│
Custom ClassLoader(自定义类加载器)
Q76: 双亲委派模型?
// 工作流程
protected Class<?> loadClass(String name, boolean resolve) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 委派父类加载器加载
c = parent.loadClass(name, false);
} else {
// 3. 委派启动类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类无法加载
}
if (c == null) {
// 4. 自己加载
c = findClass(name);
}
}
return c;
}
好处:
- 避免类重复加载
- 保护核心类安全(防止自定义java.lang.String)
破坏双亲委派:
- JDBC:DriverManager使用线程上下文类加载器
- Tomcat:每个WebApp独立类加载器
- OSGi:模块化加载
5.4 JVM调优
Q77: 常见JVM参数?
# 堆内存
-Xms512m # 堆初始大小
-Xmx2g # 堆最大大小
-Xmn256m # 新生代大小
-Xss256k # 线程栈大小
# 方法区(JDK8)
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m
# 垃圾收集器
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 最大停顿时间
# GC日志
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log
# OOM时导出堆快照
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=dump.hprof
Q78: 常见OOM及解决?
// 1. Java heap space
// 原因:对象太多,堆不够
// 解决:增大-Xmx,检查内存泄漏
// 2. GC overhead limit exceeded
// 原因:GC花费过多时间但回收很少
// 解决:增大堆,优化代码
// 3. Metaspace
// 原因:类太多,元空间不够
// 解决:增大-XX:MaxMetaspaceSize
// 4. Unable to create new native thread
// 原因:线程数过多
// 解决:减少线程数,增大系统限制
// 5. Out of memory: Java heap space
// 排查:导出hprof文件,使用MAT或jvisualvm分析
Q79: JVM监控工具?
# 命令行工具
jps # 查看Java进程
jstat -gc <pid> # GC统计
jinfo <pid> # JVM参数
jmap -histo <pid> # 对象统计
jmap -dump:format=b,file=dump.hprof <pid> # 导出堆快照
jstack <pid> # 线程栈
# GUI工具
jconsole # JMX监控
jvisualvm # 综合分析
VisualVM # jvisualvm升级版
MAT # 内存分析
Arthas # 阿里开源诊断工具
第六部分:Spring框架
6.1 Spring核心
Q80: 什么是IOC?
Inversion of Control(控制反转):对象的创建和管理交给Spring容器
// 传统方式:手动创建
UserService userService = new UserServiceImpl();
// Spring方式:依赖注入
@Service
public class UserController {
@Autowired
private UserService userService; // Spring自动注入
}
// 或构造器注入(推荐)
@Service
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
}
Q81: BeanFactory和ApplicationContext的区别?
| 特性 | BeanFactory | ApplicationContext |
|---|---|---|
| 初始化 | 延迟初始化 | 容器启动时初始化 |
| 国际化 | 不支持 | 支持 |
| 事件机制 | 不支持 | 支持 |
| AOP | 不支持 | 支持 |
| 使用场景 | 资源受限 | 企业应用 |
// BeanFactory
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("beans.xml"));
UserService userService = (UserService) factory.getBean("userService");
// ApplicationContext
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = context.getBean(UserService.class);
Q82: Spring Bean的作用域?
| 作用域 | 说明 |
|---|---|
| singleton | 单例(默认) |
| prototype | 每次获取创建新对象 |
| request | HTTP请求 |
| session | HTTP会话 |
| application | ServletContext生命周期 |
| websocket | WebSocket生命周期 |
@Service
@Scope("prototype")
public class PrototypeBean {}
Q83: Spring Bean的生命周期?
1. 实例化(Instantiation)
├── 构造器
└── BeanPostProcessor.postProcessBeforeInstantiation
2. 属性赋值(Populate)
└── 依赖注入
3. 初始化(Initialization)
├── BeanPostProcessor.postProcessBeforeInitialization
├── @PostConstruct
├── InitializingBean.afterPropertiesSet
├── init-method
└── BeanPostProcessor.postProcessAfterInitialization
4. 使用(Use)
5. 销毁(Destruction)
├── @PreDestroy
├── DisposableBean.destroy
└── destroy-method
6.2 AOP
Q84: 什么是AOP?
Aspect Oriented Programming(面向切面编程):将横切关注点模块化
// 核心概念
// - 切面(Aspect):横切关注点的模块化
// - 切入点(Pointcut):在哪些地方执行
// - 通知(Advice):在什么时候执行
// - 连接点(Joinpoint):程序执行的特定点
@Aspect
@Component
public class LoggingAspect {
// 切入点表达式
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethod() {}
// 前置通知
@Before("serviceMethod()")
public void before(JoinPoint joinPoint) {
System.out.println("Before: " + joinPoint.getSignature());
}
// 后置通知
@AfterReturning(pointcut = "serviceMethod()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After returning: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "serviceMethod()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("After throwing: " + ex);
}
// 最终通知
@After("serviceMethod()")
public void after(JoinPoint joinPoint) {
System.out.println("After: " + joinPoint.getSignature());
}
// 环绕通知
@Around("serviceMethod()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Around before");
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("Around after");
return result;
}
}
Q85: 切入点表达式?
// 匹配方法执行
execution(modifiers? return-type declaring-type? method-name(param-types) throws?)
// 示例
execution(public * *(..)) // 所有public方法
execution(* set*(..)) // 所有set开头的方法
execution(* com.example.service.*.*(..)) // service包下所有方法
execution(* com.example.service..*.*(..)) // service包及子包下所有方法
execution(* com.example.service.UserService.*(..)) // UserService的所有方法
execution(* com.example.service.UserService+.*(..)) // UserService及子类的所有方法
execution(* save*(String, ..)) // 第一个参数是String的save开头方法
// 其他切入点
@within(com.example.anno.MyAnnotation) // 标注了注解的类内的方法
@annotation(com.example.anno.MyAnnotation) // 标注了注解的方法
bean(userService) // 特定名称的Bean
args(String, ..) // 参数匹配
6.3 Spring事务
Q86: Spring事务的实现方式?
// 1. 声明式事务(推荐)
@Service
public class UserService {
@Transactional
public void saveUser(User user) {
// 业务逻辑
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(Log log) {
// 独立事务
}
}
// 2. 编程式事务
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void saveUser(User user) {
transactionTemplate.execute(status -> {
try {
// 业务逻辑
} catch (Exception e) {
status.setRollbackOnly();
}
return null;
});
}
}
Q87: 事务传播行为?
| 传播行为 | 说明 |
|---|---|
| REQUIRED(默认) | 有事务就加入,没有就新建 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 |
| SUPPORTS | 有事务就加入,没有就非事务执行 |
| NOT_SUPPORTED | 非事务执行,挂起当前事务 |
| MANDATORY | 必须在事务中,否则抛异常 |
| NEVER | 必须非事务,否则抛异常 |
| NESTED | 嵌套事务(保存点) |
// 场景:主事务失败回滚,子事务独立提交
@Transactional
public void main() {
// 主逻辑
sub(); // 即使这里成功,主事务失败也会回滚
// 如果sub用REQUIRES_NEW,sub独立事务不会回滚
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sub() {
// 子逻辑
}
Q88: 事务失效的场景?
@Service
public class UserService {
// 1. 方法不是public
@Transactional
private void method1() {} // 失效
// 2. 同类方法自调用
public void method2() {
this.method3(); // 失效,未经过代理
}
@Transactional
public void method3() {}
// 3. 异常被捕获
@Transactional
public void method4() {
try {
// 业务逻辑
throw new RuntimeException();
} catch (Exception e) {
// 异常被捕获,事务不回滚
}
}
// 4. 抛出checked异常
@Transactional
public void method5() throws Exception {
throw new Exception(); // 默认只回滚RuntimeException
}
// 解决:指定回滚异常
@Transactional(rollbackFor = Exception.class)
public void method6() throws Exception {
throw new Exception(); // 会回滚
}
// 5. 数据库不支持事务
// MySQL的MyISAM引擎不支持事务
}
6.4 Spring MVC
Q89: Spring MVC工作流程?
1. 用户请求 → DispatcherServlet
2. DispatcherServlet → HandlerMapping(获取Handler)
3. HandlerMapping → 返回HandlerExecutionChain(Handler + Interceptors)
4. DispatcherServlet → HandlerAdapter(执行Handler)
5. HandlerAdapter → Controller(业务处理)
6. Controller → 返回ModelAndView
7. DispatcherServlet → ViewResolver(解析视图)
8. ViewResolver → 返回View
9. DispatcherServlet → View(渲染视图)
10. View → 返回响应
Q90: Spring MVC常用注解?
@Controller // 控制器
@RestController // = @Controller + @ResponseBody
@RequestMapping("/api") // 路径映射
@GetMapping // GET请求
@PostMapping // POST请求
@PutMapping // PUT请求
@DeleteMapping // DELETE请求
@RequestParam // 请求参数
@PathVariable // 路径变量
@RequestBody // 请求体
@RequestHeader // 请求头
@CookieValue // Cookie值
@ResponseBody // 响应体(JSON)
@ResponseStatus // 响应状态码
@ControllerAdvice // 全局异常处理
@ExceptionHandler // 异常处理方法
6.5 Spring Boot
Q91: Spring Boot自动装配原理?
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// @SpringBootApplication = @SpringBootConfiguration
// + @EnableAutoConfiguration
// + @ComponentScan
// @EnableAutoConfiguration核心
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
// AutoConfigurationImportSelector读取
// META-INF/spring.factories
// 加载EnableAutoConfiguration配置的类
// 示例:spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
// 条件装配
@Configuration
@ConditionalOnClass(DataSource.class)
@ConditionalOnMissingBean(DataSource.class)
public class DataSourceAutoConfiguration {
// 只有当classpath存在DataSource且容器中没有DataSource时才装配
}
Q92: Spring Boot常用注解?
// 配置类
@Configuration // 配置类
@Bean // Bean定义
@ConfigurationProperties // 配置属性绑定
@Value("${app.name}") // 属性注入
// 条件装配
@ConditionalOnClass // 类路径存在指定类
@ConditionalOnMissingBean // 容器中不存在指定Bean
@ConditionalOnProperty // 属性满足条件
@ConditionalOnWebApplication // Web应用
// 启动类
@SpringBootApplication // 启动类注解
@EnableScheduling // 开启定时任务
@EnableAsync // 开启异步
@EnableCaching // 开启缓存
第七部分:数据库
7.1 MySQL基础
Q93: MySQL存储引擎?
| 引擎 | 事务 | 锁 | 外键 | 适用场景 |
|---|---|---|---|---|
| InnoDB | 支持 | 行锁 | 支持 | OLTP,高并发 |
| MyISAM | 不支持 | 表锁 | 不支持 | 读多写少 |
| Memory | 不支持 | 表锁 | 不支持 | 临时表 |
-- 查看存储引擎
SHOW ENGINES;
-- 指定表的存储引擎
CREATE TABLE t (id INT) ENGINE=InnoDB;
Q94: MySQL索引类型?
| 索引类型 | 说明 |
|---|---|
| 主键索引 | 唯一且非空,聚簇索引 |
| 唯一索引 | 唯一,可为空 |
| 普通索引 | 无限制 |
| 组合索引 | 多列组合 |
| 全文索引 | 文本搜索 |
| 空间索引 | 地理数据 |
-- 创建索引
CREATE INDEX idx_name ON t(name);
CREATE UNIQUE INDEX idx_email ON t(email);
CREATE INDEX idx_name_age ON t(name, age); -- 组合索引
-- 最左前缀原则
-- idx_name_age(name, age)
SELECT * FROM t WHERE name = 'Tom'; -- 命中
SELECT * FROM t WHERE name = 'Tom' AND age = 20; -- 命中
SELECT * FROM t WHERE age = 20; -- 不命中
SELECT * FROM t WHERE name LIKE 'T%'; -- 命中
SELECT * FROM t WHERE name LIKE '%T'; -- 不命中
Q95: 索引的底层数据结构?
B+树(MySQL InnoDB使用)
特点:
1. 非叶子节点只存储键值和指针
2. 叶子节点存储所有数据,形成有序链表
3. 叶子节点通过指针连接,便于范围查询
4. 树的高度低(通常3层),减少磁盘I/O
优势:
1. 单节点存储更多key,树更矮
2. 叶子节点链表,范围查询高效
3. 磁盘I/O次数少
Q96: 聚簇索引和非聚簇索引?
| 特性 | 聚簇索引 | 非聚簇索引 |
|---|---|---|
| 数据存储 | 叶子节点存数据 | 叶子节点存主键值 |
| 数量 | 1个 | 多个 |
| 查询 | 直接获取 | 回表查询 |
| 存储 | 按主键顺序存储 | 独立存储 |
-- 回表查询
-- 非聚簇索引idx_name(name)
SELECT * FROM t WHERE name = 'Tom';
-- 1. 在idx_name中查找name='Tom'的主键id
-- 2. 在聚簇索引中查找id对应的数据(回表)
-- 覆盖索引(避免回表)
SELECT id, name FROM t WHERE name = 'Tom'; -- 只查索引列,无需回表
7.2 事务
Q97: 事务的ACID特性?
| 特性 | 说明 |
|---|---|
| 原子性(Atomicity) | 全部成功或全部失败 |
| 一致性(Consistency) | 数据状态一致 |
| 隔离性(Isolation) | 事务间互不干扰 |
| 持久性(Durability) | 提交后永久保存 |
Q98: 事务隔离级别?
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交(READ UNCOMMITTED) | ✓ | ✓ | ✓ |
| 读已提交(READ COMMITTED) | ✗ | ✓ | ✓ |
| 可重复读(REPEATABLE READ) | ✗ | ✗ | ✓(MySQL InnoDB解决了) |
| 串行化(SERIALIZABLE) | ✗ | ✗ | ✗ |
-- MySQL默认隔离级别
SELECT @@transaction_isolation;
-- 设置隔离级别
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 事务操作
START TRANSACTION;
COMMIT;
ROLLBACK;
Q99: MVCC是什么?
Multi-Version Concurrency Control(多版本并发控制):不加锁实现并发读
-- 核心概念
-- 1. 隐藏字段
-- - DB_TRX_ID:最后插入或更新的事务ID
-- - DB_ROLL_PTR:回滚指针,指向undo日志
-- 2. undo日志
-- 记录数据的历史版本,用于回滚和读历史数据
-- 3. Read View
-- 决定可见性,包含:
-- - m_ids:活跃事务ID列表
-- - min_trx_id:最小活跃事务ID
-- - max_trx_id:下一个事务ID
-- - creator_trx_id:创建该Read View的事务ID
-- 可见性判断
-- 如果 DB_TRX_ID < min_trx_id,可见
-- 如果 DB_TRX_ID >= max_trx_id,不可见
-- 如果 DB_TRX_ID 在 m_ids 中,不可见
-- 否则可见
7.3 SQL优化
Q100: SQL优化方法?
-- 1. 使用EXPLAIN分析执行计划
EXPLAIN SELECT * FROM t WHERE name = 'Tom';
-- 关键字段
-- id:执行顺序
-- type:访问类型(const > eq_ref > ref > range > index > ALL)
-- key:使用的索引
-- rows:预估扫描行数
-- Extra:额外信息(Using index = 覆盖索引)
-- 2. 避免SELECT *
SELECT id, name FROM t; -- 好
SELECT * FROM t; -- 差
-- 3. 避免索引失效
-- 函数操作
WHERE YEAR(create_time) = 2024; -- 索引失效
WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01'; -- 好
-- 隐式转换
WHERE phone = 13800138000; -- phone是varchar,索引失效
WHERE phone = '13800138000'; -- 好
-- LIKE左模糊
WHERE name LIKE '%Tom'; -- 索引失效
WHERE name LIKE 'Tom%'; -- 好
-- OR连接
WHERE name = 'Tom' OR age = 20; -- 可能索引失效
-- 改为UNION ALL
-- 4. LIMIT分页优化
SELECT * FROM t LIMIT 10000, 10; -- 差,扫描前10010行
-- 优化:使用子查询
SELECT * FROM t WHERE id >= (
SELECT id FROM t LIMIT 10000, 1
) LIMIT 10;
-- 5. 使用覆盖索引
SELECT id, name FROM t WHERE name = 'Tom'; -- 好
-- 6. 避免大事务
-- 拆分大事务为小事务
第八部分:Redis缓存
8.1 Redis基础
Q101: Redis数据类型?
| 类型 | 说明 | 使用场景 |
|---|---|---|
| String | 字符串、数字 | 缓存、计数器、分布式锁 |
| Hash | 哈希表 | 对象存储 |
| List | 列表 | 消息队列、列表 |
| Set | 无序集合 | 标签、交集并集 |
| ZSet | 有序集合 | 排行榜 |
| Bitmap | 位图 | 签到、统计 |
| HyperLogLog | 基数统计 | UV统计 |
| Geo | 地理位置 | 位置相关 |
| Stream | 流 | 消息队列 |
# String
SET key value
GET key
INCR key
SETEX key 60 value # 设置60秒过期
# Hash
HSET user:1 name Tom age 20
HGET user:1 name
HMGET user:1 name age
# List
LPUSH list a b c
RPOP list
LRANGE list 0 -1
# Set
SADD set a b c
SMEMBERS set
SINTER set1 set2
# ZSet
ZADD zset 1 a 2 b 3 c
ZRANGE zset 0 -1 WITHSCORES
ZREVRANGE zset 0 9 # 排行榜前10
Q102: Redis为什么快?
- 内存存储:数据在内存中,读写速度快
- 单线程:避免线程切换和锁竞争
- IO多路复用:epoll实现高并发
- 高效数据结构:SDS、跳表、压缩列表等
8.2 缓存问题
Q103: 缓存穿透?
问题:查询不存在的数据,缓存和数据库都无数据,请求直达数据库
解决方案:
// 1. 缓存空值
public Object get(String key) {
Object value = redis.get(key);
if (value != null) {
return value;
}
Object dbValue = db.query(key);
if (dbValue == null) {
redis.setex(key, 60, ""); // 缓存空值,短过期时间
} else {
redis.set(key, dbValue);
}
return dbValue;
}
// 2. 布隆过滤器
public Object get(String key) {
if (!bloomFilter.mightContain(key)) {
return null; // 一定不存在
}
// 可能存在,查询缓存和数据库
}
Q104: 缓存击穿?
问题:热点数据过期,大量请求直达数据库
解决方案:
// 1. 热点数据永不过期
redis.set(key, value); // 不设置过期时间
// 2. 互斥锁
public Object get(String key) {
Object value = redis.get(key);
if (value != null) {
return value;
}
String lockKey = "lock:" + key;
try {
// 尝试获取锁
if (redis.setnx(lockKey, "1", 10)) {
value = db.query(key);
redis.set(key, value, 3600);
return value;
} else {
// 等待重试
Thread.sleep(50);
return get(key);
}
} finally {
redis.del(lockKey);
}
}
Q105: 缓存雪崩?
问题:大量缓存同时过期,或Redis宕机
解决方案:
// 1. 过期时间随机
int expire = 3600 + new Random().nextInt(600); // 1小时±10分钟
redis.set(key, value, expire);
// 2. 高可用架构
// 主从复制 + 哨兵 或 Redis Cluster
// 3. 熔断降级
@HystrixCommand(fallbackMethod = "getFallback")
public Object get(String key) {
// 正常逻辑
}
public Object getFallback(String key) {
// 降级逻辑:返回默认值或从本地缓存读取
}
8.3 Redis持久化
Q106: RDB和AOF的区别?
| 特性 | RDB | AOF |
|---|---|---|
| 存储内容 | 内存快照 | 写命令日志 |
| 文件大小 | 小 | 大 |
| 恢复速度 | 快 | 慢 |
| 数据完整性 | 可能丢失 | 更完整 |
| 性能影响 | 小 | 大 |
# RDB配置
save 900 1 # 900秒内有1次写操作就保存
save 300 10 # 300秒内有10次写操作
save 60 10000 # 60秒内有10000次写操作
# AOF配置
appendonly yes
appendfsync everysec # 每秒同步
# 混合持久化(推荐)
aof-use-rdb-preamble yes
第九部分:分布式与微服务
9.1 分布式锁
Q107: 如何实现分布式锁?
// Redis实现
public boolean tryLock(String key, String value, long expire) {
// SET NX EX原子操作
return redis.setnx(key, value, expire);
}
public boolean releaseLock(String key, String value) {
// Lua脚本保证原子性
String script =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
return redis.eval(script, key, value);
}
// Redisson(推荐)
RLock lock = redisson.getLock("myLock");
try {
lock.lock();
// 业务逻辑
} finally {
lock.unlock();
}
// Zookeeper实现
InterProcessMutex lock = new InterProcessMutex(client, "/locks/myLock");
try {
lock.acquire();
// 业务逻辑
} finally {
lock.release();
}
9.2 消息队列
Q108: 消息队列的使用场景?
| 场景 | 说明 |
|---|---|
| 异步处理 | 发送邮件、短信等异步执行 |
| 应用解耦 | 生产者和消费者独立部署 |
| 流量削峰 | 高峰时消息堆积,平滑处理 |
| 日志处理 | 日志收集和分析 |
Q109: 如何保证消息不丢失?
生产者端:
1. 发送确认(ACK)
2. 本地消息表 + 定时任务
Broker端:
1. 同步刷盘
2. 多副本同步复制
消费者端:
1. 手动确认(ACK)
2. 幂等处理
Q110: 如何保证消息顺序?
// 方案1:单队列单消费者
// 缺点:性能差
// 方案2:分区队列
// 同一业务ID的消息发到同一分区
int partition = orderId.hashCode() % partitionCount;
producer.send(topic, partition, key, message);
// 消费者单线程处理同一分区的消息
9.3 微服务
Q111: 微服务架构组件?
| 组件 | 作用 | 实现 |
|---|---|---|
| 注册中心 | 服务注册与发现 | Nacos、Eureka、Consul |
| 配置中心 | 统一配置管理 | Nacos、Apollo |
| 网关 | 统一入口、路由、限流 | Spring Cloud Gateway |
| 负载均衡 | 请求分发 | Ribbon、LoadBalancer |
| 服务调用 | 服务间通信 | Feign、Dubbo |
| 熔断降级 | 故障保护 | Sentinel、Hystrix |
| 链路追踪 | 调用链追踪 | Skywalking、Zipkin |
Q112: CAP理论?
| 特性 | 说明 |
|---|---|
| Consistency(一致性) | 所有节点数据一致 |
| Availability(可用性) | 每个请求都有响应 |
| Partition Tolerance(分区容错) | 网络分区时系统继续运行 |
选择:
- CA:单机数据库(MySQL单机)
- CP:分布式数据库(HBase、MongoDB)
- AP:注册中心(Eureka、Nacos AP模式)
第十部分:设计模式
10.1 创建型模式
Q113: 单例模式?
// 饿汉式(线程安全)
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
// 双重检查锁
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 静态内部类(推荐)
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
// 枚举(最佳实践)
public enum Singleton {
INSTANCE;
}
Q114: 工厂模式?
// 简单工厂
public class ShapeFactory {
public static Shape createShape(String type) {
switch (type) {
case "circle": return new Circle();
case "square": return new Square();
default: throw new IllegalArgumentException();
}
}
}
// 工厂方法
public interface ShapeFactory {
Shape createShape();
}
public class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
// 抽象工厂
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
public class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
10.2 结构型模式
Q115: 代理模式?
// 静态代理
public interface Subject {
void request();
}
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject");
}
}
public class Proxy implements Subject {
private RealSubject realSubject;
@Override
public void request() {
before();
realSubject.request();
after();
}
private void before() { System.out.println("Before"); }
private void after() { System.out.println("After"); }
}
// JDK动态代理
public class JdkProxy implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
private void before() {}
private void after() {}
}
// CGLIB动态代理
public class CglibProxy implements MethodInterceptor {
public Object getProxy(Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
Object result = proxy.invokeSuper(obj, args);
after();
return result;
}
}
10.3 行为型模式
Q116: 策略模式?
// 定义策略接口
public interface PaymentStrategy {
void pay(int amount);
}
// 具体策略
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("支付宝支付:" + amount);
}
}
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("微信支付:" + amount);
}
}
// 上下文
public class PaymentContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void pay(int amount) {
strategy.pay(amount);
}
}
// 使用
PaymentContext context = new PaymentContext();
context.setStrategy(new AlipayStrategy());
context.pay(100);
Q117: 观察者模式?
// 定义观察者接口
public interface Observer {
void update(String message);
}
// 具体观察者
public class UserObserver implements Observer {
private String name;
public UserObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + "收到消息:" + message);
}
}
// 被观察者
public class Subject {
private List<Observer> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyAll(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// Java内置观察者模式
// Observable(被观察者) + Observer(观察者)
// 或使用事件监听机制
第十一部分:算法与数据结构
11.1 常见算法
Q118: 排序算法比较?
| 算法 | 平均时间 | 最坏时间 | 空间 | 稳定性 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 |
| 归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 |
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
| 计数排序 | O(n+k) | O(n+k) | O(k) | 稳定 |
Q119: 快速排序实现?
public void quickSort(int[] arr, int left, int right) {
if (left < right) {
int pivot = partition(arr, left, right);
quickSort(arr, left, pivot - 1);
quickSort(arr, pivot + 1, right);
}
}
private int partition(int[] arr, int left, int right) {
int pivot = arr[right];
int i = left - 1;
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) {
i++;
swap(arr, i, j);
}
}
swap(arr, i + 1, right);
return i + 1;
}
private void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
11.2 数据结构
Q120: 常见数据结构?
| 数据结构 | 特点 | 时间复杂度 |
|---|---|---|
| 数组 | 连续内存,随机访问 | 访问O(1),插入删除O(n) |
| 链表 | 非连续内存 | 访问O(n),插入删除O(1) |
| 栈 | LIFO | 入栈出栈O(1) |
| 队列 | FIFO | 入队出队O(1) |
| 哈希表 | 键值对 | 平均O(1) |
| 树 | 层次结构 | 查找O(logn) |
| 图 | 网络关系 | 取决于算法 |
结语
本文档涵盖了Java面试的核心知识点,从基础语法到高级架构,帮助系统化复习。
建议:
- 理解原理,不要死记硬背
- 结合代码实践
- 关注新技术发展
- 多做项目积累经验
祝你面试顺利!🎉