Java面试题大全(从基础到高级)

jxq
2
2026-05-01

Java面试题大全

从基础到高级,全面覆盖Java面试知识点

最后更新:2025-05-01


目录


第一部分: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修饰。

原因

  1. 安全性:String常用于敏感信息(URL、文件路径、密码),不可继承避免被恶意篡改
  2. 效率:不可变性支持字符串常量池,节省内存
  3. 线程安全:不可变对象天生线程安全
  4. 哈希缓存: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个

  1. 如果字符串常量池中不存在"abc":

    • 常量池中创建"abc"(1个)
    • 堆中创建String对象(1个)
    • 共2个对象
  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()的关系?

规则

  1. equals返回true,hashCode必须相同
  2. hashCode相同,equals不一定返回true(哈希冲突)
  3. 重写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特点

  1. 属于类,不属于对象
  2. 随类加载而加载
  3. 优先于对象存在
  4. 可以通过类名直接访问

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  // 异常更宽泛
}

重写规则

  1. 方法名、参数列表必须相同
  2. 返回类型相同或为子类(协变返回类型)
  3. 访问权限不能更严格
  4. 抛出异常不能更宽泛
  5. final方法不能重写
  6. 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;
    }
}

特点

  1. 名字与类名相同
  2. 没有返回类型(不是void)
  3. 可以重载
  4. 不能被继承
  5. 默认提供无参构造(如果没写任何构造器)

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; // 等待状态
    }
}

核心原理

  1. state:同步状态,0表示未占用,>0表示占用
  2. CLH队列:双向链表,存储等待线程
  3. 独占模式:只有一个线程能获取锁(ReentrantLock)
  4. 共享模式:多个线程可同时获取锁(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;
}

好处

  1. 避免类重复加载
  2. 保护核心类安全(防止自定义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为什么快?

  1. 内存存储:数据在内存中,读写速度快
  2. 单线程:避免线程切换和锁竞争
  3. IO多路复用:epoll实现高并发
  4. 高效数据结构: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面试的核心知识点,从基础语法到高级架构,帮助系统化复习。

建议

  1. 理解原理,不要死记硬背
  2. 结合代码实践
  3. 关注新技术发展
  4. 多做项目积累经验

祝你面试顺利!🎉

动物装饰