八股取士--java

Java编程基础与高级特性解析

基础知识

1. Java中的基本数据类型及取值范围

Java中的基本数据类型有8种,每种数据类型的取值范围如下:

  • byte:8位,取值范围:-128 到 127
  • short:16位,取值范围:-32,768 到 32,767
  • int:32位,取值范围:-2^31 到 2^31 - 1(即 -2,147,483,648 到 2,147,483,647)
  • long:64位,取值范围:-2^63 到 2^63 - 1(即 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807)
  • float:32位,取值范围:±1.4E-45 到 ±3.4E+38(单精度浮点数)
  • double:64位,取值范围:±4.9E-324 到 ±1.8E+308(双精度浮点数)
  • char:16位,取值范围:0 到 65,535(Unicode字符集)
  • boolean:无具体取值范围,只有两个值:truefalse

2. 自动装箱和拆箱

  • 自动装箱(Autoboxing):Java会自动将基本数据类型转换为其对应的包装类。例如,int 自动转换为 Integerchar 自动转换为 Character 等。例如:

    Integer i = 10;  // 自动装箱
    
  • 拆箱(Unboxing):Java会自动将包装类转换为其对应的基本数据类型。例如,Integer 自动转换为 intCharacter 自动转换为 char 等。例如:

    int j = i;  // 拆箱
    

3. Java中的String和StringBuilder的区别

  • String

    • 不可变(immutable),每次修改都会生成新的对象。
    • 使用字符串常量池优化内存使用,多个相同的字符串引用同一个对象。
    • 性能较低,特别是多次修改字符串的情况。
  • StringBuilder

    • 可变(mutable),可以对字符串进行修改而不生成新的对象。
    • 适用于多次修改字符串的场景,性能更好。
    • 线程不安全,若在多线程环境下使用需额外处理。

4. Java中的equals和hashCode方法的关系

  • equals() 方法:用于比较两个对象的内容是否相等。默认情况下,equals() 比较的是对象的引用(即内存地址),而不是对象的内容。
  • hashCode() 方法:返回对象的哈希码,用于对象的快速查找(如 HashMapHashSet 等集合类)。

关系

  • 如果两个对象通过 equals() 方法被认为是相等的,那么它们的 hashCode() 也必须相等。
  • 反过来,两个对象的 hashCode() 不相等,那么它们通过 equals() 方法肯定不相等。

5. 如何在Java中创建一个线程?使用Runnable和Thread的不同

创建线程有两种方式:

  1. 继承Thread类:通过继承 Thread 类并重写 run() 方法。

    class MyThread extends Thread {
        public void run() {
            System.out.println("Thread is running");
        }
    }
    MyThread t = new MyThread();
    t.start();  // 启动线程
    
  2. 实现Runnable接口:实现 Runnable 接口并重写 run() 方法,然后将其传递给 Thread 对象。

    class MyRunnable implements Runnable {
        public void run() {
            System.out.println("Thread is running");
        }
    }
    MyRunnable myRunnable = new MyRunnable();
    Thread t = new Thread(myRunnable);
    t.start();  // 启动线程
    

区别

  • 使用 Thread 类时,类已经继承了 Thread,因此不能继承其他类。而使用 Runnable 接口时,可以实现多个接口或继承其他类。
  • Runnable 是一个更轻量级的方式,推荐用于线程池和高性能应用。

6. 什么是类加载器?如何自定义类加载器?

  • 类加载器 是 Java 中负责加载类的组件。它通过查找并加载字节码文件(.class 文件)到 JVM 中来支持类的动态加载。常见的类加载器有:

    • 启动类加载器(Bootstrap ClassLoader)
    • 扩展类加载器(Extension ClassLoader)
    • 应用程序类加载器(AppClassLoader)
  • 自定义类加载器:可以通过继承 ClassLoader 类并重写 findClass() 方法来自定义类加载器。自定义类加载器可以控制类的加载方式,如从网络、数据库等源加载类。

    示例:

    class MyClassLoader extends ClassLoader {
        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException {
            // 自定义加载类的逻辑
            byte[] b = ...;  // 获取类的字节码
            return defineClass(name, b, 0, b.length);
        }
    }
    

7. 简述Java中static关键字的作用

  • 静态变量(static variable):属于类而不是对象,所有实例共享同一个静态变量。
  • 静态方法(static method):可以通过类名直接访问,不需要实例化对象。静态方法只能访问静态变量和静态方法。
  • 静态代码块(static block):在类加载时执行一次,用于初始化静态资源。
  • 静态内部类(static inner class):不依赖于外部类的实例,可以直接通过外部类访问。

8. Java中的异常类型及区别

Java中的异常分为两大类:

  1. 检查异常(Checked Exception)

    • 继承自 Exception 类,程序必须显式处理(通过 try-catchthrows)。
    • 如:IOException, SQLException
  2. 非检查异常(Unchecked Exception)

    • 继承自 RuntimeException 类,程序不需要强制处理。
    • 如:NullPointerException, ArrayIndexOutOfBoundsException

区别

  • 检查异常通常是程序无法预料的异常,必须处理;而非检查异常一般是编程错误或可以避免的情况,通常不强制要求处理。

9. 解释Java中的try-catch-finally机制,finally块一定会执行吗?

  • try 块:用于执行可能抛出异常的代码。
  • catch 块:用于捕获并处理异常。
  • finally 块:无论是否发生异常,都会执行,用于资源清理(如关闭文件、数据库连接等)。

finally块一定会执行吗?

  • 在正常情况下,finally 块会执行,但如果在 trycatch 块中使用了 System.exit() 或 JVM 崩溃等情况,finally 可能不会执行。

10. 什么是Java中的泛型?为什么要使用泛型?

  • 泛型:是Java的一个特性,允许在定义类、接口和方法时使用类型参数。泛型使得代码更加通用和类型安全,避免了运行时类型转换错误。

    示例:

    class Box<T> {
        private T value;
        public void set(T value) { this.value = value; }
        public T get() { return value; }
    }
    

为什么要使用泛型?

  • 类型安全:避免类型转换异常,编译器能在编译时检查类型。
  • 代码复用:可以编写通用代码,处理多种数据类型,而无需编写重复的代码。

面向对象

1. 什么是面向对象编程?它的四大基本特性是什么?

面向对象编程(OOP)是一种编程范式,基于“对象”这一概念,将现实世界的实体和行为映射为计算机程序中的对象和方法。OOP 的四大基本特性是:

  • 封装(Encapsulation)

    • 封装是指将数据(属性)和对数据的操作(方法)包装成一个整体(类),并对外界隐藏实现细节,仅通过公开的接口与外部进行交互。
    • 优点:提高了安全性,避免了直接访问和修改内部数据的风险。
  • 继承(Inheritance)

    • 继承是指一个类(子类)继承另一个类(父类)的属性和方法。子类可以重用父类的代码,也可以增加新的功能或修改父类的功能。
    • 优点:提高了代码复用性和可扩展性。
  • 多态(Polymorphism)

    • 多态指不同的对象可以通过相同的接口执行不同的操作。多态可以通过方法重载(编译时多态)和方法重写(运行时多态)实现。
    • 优点:增加了系统的灵活性和可扩展性。
  • 抽象(Abstraction)

    • 抽象是指通过将事物的共性提取出来,隐藏掉不必要的细节,简化问题的复杂度。抽象可以通过抽象类和接口来实现。
    • 优点:提高了代码的可维护性和可扩展性。

2. Java中的继承和多态有什么特点?

  • 继承(Inheritance)

    • 子类继承父类的属性和方法(不包括构造函数和私有成员)。
    • 子类可以重写(Override)父类的方法,从而实现更具体的行为。
    • Java 是单继承,即一个类只能继承一个父类,但可以通过接口实现多继承。
  • 多态(Polymorphism)

    • 多态允许同一个接口调用不同的实现。多态分为:

      • 编译时多态(方法重载,Method Overloading):通过方法名和参数的不同来区分。
      • 运行时多态(方法重写,Method Overriding):父类引用指向子类对象,调用的是子类重写的方法。
    • Java 使用动态绑定(Dynamic Binding)来决定在运行时调用哪个方法。

3. 接口(Interface)和抽象类(Abstract Class)的区别

  • 接口(Interface)

    • 只能声明方法,不提供实现,所有方法默认是 public abstract
    • 可以有常量(public static final)和默认方法(default)。
    • 类可以实现多个接口,实现多继承的特性。
    • 适用于多个类之间的共同行为的抽象。
  • 抽象类(Abstract Class)

    • 可以有已实现的方法和未实现的方法(抽象方法)。
    • 可以有实例变量(非 static),而接口不能。
    • 类只能继承一个抽象类,但可以实现多个接口。
    • 适用于存在公共部分的类之间的共享功能。

4. 如何实现接口中的默认方法?

接口中的默认方法使用 default 关键字声明,并且可以提供方法的默认实现。实现接口的类可以选择是否重写默认方法。

示例:

interface MyInterface {
    default void sayHello() {
        System.out.println("Hello, World!");
    }
}

class MyClass implements MyInterface {
    // 可以选择重写默认方法
    public void sayHello() {
        System.out.println("Hello from MyClass!");
    }
}

5. 什么是构造函数,Java中如何使用构造函数进行对象的初始化?

  • 构造函数(Constructor)是一个特殊的方法,用于在创建对象时初始化对象的状态。构造函数的名称必须与类名相同,并且没有返回类型。

  • 如何使用构造函数

    • 如果没有定义构造函数,Java 会提供一个默认构造函数。
    • 可以定义带参数的构造函数来初始化对象的属性。

示例:

class Person {
    String name;
    int age;

    // 构造函数
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

Person p = new Person("John", 25);  // 使用构造函数初始化

6. 什么是方法重载和方法重写?它们的区别是什么?

  • 方法重载(Overloading)

    • 同一个类中,方法名相同,但参数类型、个数或顺序不同。与返回类型无关。
    • 发生在编译时,属于编译时多态。

示例:

class Calculator {
    public int add(int a, int b) { return a + b; }
    public double add(double a, double b) { return a + b; }
}
  • 方法重写(Overriding)

    • 子类重新实现父类的方法,以便提供特定的实现。
    • 发生在运行时,属于运行时多态。
    • 重写的方法必须具有相同的方法签名(方法名、参数类型和顺序相同)。

示例:

class Animal {
    public void sound() { System.out.println("Animal sound"); }
}

class Dog extends Animal {
    @Override
    public void sound() { System.out.println("Bark"); }
}

7. 什么是单例模式?如何在Java中实现单例模式?

  • 单例模式是一种设计模式,确保一个类只有一个实例,并提供全局访问点。

  • 实现单例模式

    • 懒汉式(线程不安全):

      class Singleton {
          private static Singleton instance;
          
          private Singleton() {}
          
          public static Singleton getInstance() {
              if (instance == null) {
                  instance = new Singleton();
              }
              return instance;
          }
      }
      
    • 饿汉式(线程安全):

      class Singleton {
          private static final Singleton instance = new Singleton();
          
          private Singleton() {}
          
          public static Singleton getInstance() {
              return instance;
          }
      }
      

8. 如何防止类被继承?

在Java中,通过 final 关键字可以防止类被继承。被 final 修饰的类不能作为父类继承。

示例:

final class MyClass {
    // 无法继承此类
}

9. 什么是类的深拷贝和浅拷贝?

  • 浅拷贝(Shallow Copy)

    • 创建一个新对象,但对象中的引用类型字段指向原对象中的同一地址。
    • 对引用类型字段的修改会影响到原对象。

示例:

class Person {
    String name;
    int age;
}

Person p1 = new Person();
p1.name = "John";
p1.age = 25;

Person p2 = p1;  // 浅拷贝,p2 和 p1 指向同一个对象
  • 深拷贝(Deep Copy)

    • 创建一个新对象,并且递归地拷贝对象中的所有字段,包括引用类型字段指向的对象,确保新对象与原对象完全独立。

示例:

class Person implements Cloneable {
    String name;
    int age;

    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Person p1 = new Person();
p1.name = "John";
p1.age = 25;

Person p2 = (Person) p1.clone();  // 深拷贝

集合框架

1. Java中的集合类有哪些?它们之间的区别是什么?

Java集合框架包含许多不同的类和接口,用于存储和操作数据。常见的集合类按功能和特性可分为以下几类:

  • List:有序且可重复的集合,元素按插入顺序排列,常见实现类包括 ArrayListLinkedListVector
  • Set:不允许重复的集合,元素不按顺序排列。常见实现类有 HashSetLinkedHashSetTreeSet
  • Queue:先进先出(FIFO)顺序的集合,常见实现类有 LinkedList(也实现了 List 接口)和 PriorityQueue
  • Map:键值对集合,常见实现类包括 HashMapTreeMapLinkedHashMap
  • SortedSetSortedMap:这些接口提供了对集合元素的排序操作,常见实现类包括 TreeSetTreeMap

2. ArrayList和LinkedList有什么区别?

ArrayListLinkedList 都实现了 List 接口,但它们的内部结构和性能特点不同:

  • ArrayList

    • 内部使用数组来存储元素。
    • 支持按索引访问,时间复杂度是 O(1)。
    • 在尾部添加元素时性能较好,但在中间或头部插入/删除元素时性能较差,因为数组需要移动元素,时间复杂度为 O(n)。
    • 空间占用较少。
  • LinkedList

    • 内部使用双向链表存储元素。
    • 插入和删除元素非常高效,时间复杂度为 O(1)。
    • 按索引访问时,需要遍历链表,时间复杂度为 O(n)。
    • 空间占用较大,因为每个元素需要额外存储指向前一个和后一个元素的指针。

3. HashMap和TreeMap有什么区别?

  • HashMap

    • 使用哈希表实现,元素的顺序不保证。
    • 支持 null 键和 null 值。
    • 查找、插入和删除操作的时间复杂度是 O(1),但由于哈希冲突,实际性能可能会有所波动。
  • TreeMap

    • 使用红黑树实现,元素按键的自然顺序或通过提供的比较器排序。
    • 不支持 null 键,但可以支持 null 值。
    • 查找、插入和删除操作的时间复杂度是 O(log n),保证有序。

4. Set和List有什么区别?

  • Set

    • 不允许重复的元素,任何重复的元素在 Set 中都会被自动去重。
    • 元素的顺序不一定是固定的。
    • 常见实现类包括 HashSetLinkedHashSetTreeSet
  • List

    • 允许重复的元素。
    • 保证元素的插入顺序。
    • 常见实现类包括 ArrayListLinkedListVector

5. 什么是Map接口,HashMap和Hashtable的区别是什么?

  • Map接口

    • 用于存储键值对数据,每个元素包含一个唯一的键和一个值。Map 不继承 Collection 接口,它本身是一个独立的接口。
    • 常见实现类包括 HashMapTreeMapLinkedHashMapHashtable
  • HashMapHashtable 的区别:

    • 线程安全Hashtable 是线程安全的,而 HashMap 是不安全的,HashMap 在并发环境下需要额外同步。
    • nullHashMap 允许一个 null 键和多个 null 值,而 Hashtable 不允许任何 null 键或 null 值。
    • 性能HashMap 由于不涉及同步机制,性能通常优于 Hashtable

6. 什么是迭代器(Iterator)?如何使用迭代器遍历集合?

  • 迭代器(Iterator) 是一种用于遍历集合的工具,它提供了一些方法(如 hasNext()next())来顺序访问集合中的元素。

    示例:

    List<String> list = new ArrayList<>();
    list.add("A");
    list.add("B");
    list.add("C");
    
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
    
    • hasNext():判断集合中是否还有元素。
    • next():返回下一个元素。
    • remove():移除当前元素(可选操作)。

7. 如何判断一个对象是否在集合中?

判断一个对象是否在集合中,可以使用集合的 contains() 方法。这个方法会调用集合中元素的 equals() 方法进行比较。

示例:

Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("C");

System.out.println(set.contains("B"));  // 返回 true
System.out.println(set.contains("D"));  // 返回 false

8. 什么是ConcurrentHashMap,它与HashMap的区别是什么?

  • ConcurrentHashMap 是一个线程安全的 Map 实现,支持高并发读写操作。
  • 它通过将数据分段(Segment)并对每个段进行独立加锁来提高并发性能,而不是对整个数据结构加锁。

与 HashMap 的区别

  • 线程安全性ConcurrentHashMap 是线程安全的,支持多线程并发读写,而 HashMap 不是线程安全的。
  • 性能:由于使用分段锁,ConcurrentHashMap 在高并发情况下性能优于 Hashtable 或同步的 HashMap
  • 操作ConcurrentHashMap 提供了更细粒度的锁定机制,避免了全表锁,支持高效的并发操作。

9. Java中的LinkedHashMap是什么?它有什么特点?

  • LinkedHashMapHashMap 的一个子类,具有以下特点:

    • 它维护了元素的插入顺序或访问顺序(如果构造时使用了 accessOrder 标志)。
    • HashMap 一样,提供常数时间的查找、插入和删除操作,但它还维护了一个双向链表,按照元素的插入顺序或访问顺序排列元素。
  • 特性

    • 插入顺序:默认情况下,LinkedHashMap 保持元素的插入顺序。
    • 访问顺序:通过设置构造参数为 trueLinkedHashMap 可以根据元素的访问顺序排列。

示例:

LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

map.get("A");  // A becomes the most recently accessed
map.put("D", 4);

System.out.println(map);  // 输出根据访问顺序排列的元素

10.HashMapTreeMapLinkedHashMapHashtable 的深度辨析


一、核心特性总览
特性HashMapTreeMapLinkedHashMapHashtable
数据结构数组+链表/红黑树红黑树数组+链表/红黑树+双向链表数组+链表
排序无序Key 自然排序/自定义插入顺序/访问顺序无序
线程安全✅ (全表锁)
Null 支持Key/Value 均可为 nullKey 不能为 nullKey/Value 均可为 nullKey/Value 均不能为 null
性能O(1) 查找/插入 (平均)O(log n) 操作O(1) 查找/插入 (平均)O(1) 操作 (锁竞争影响)
继承关系AbstractMapNavigableMapHashMap 子类Dictionary
同步方式synchronized 方法

二、实现原理深度解析
1. HashMap
  • 数据结构
    • 数组(桶) + 链表(冲突解决)
    • 链表长度 ≥ 8 且桶数量 ≥ 64 时,链表转红黑树(优化极端哈希冲突)
  • 哈希算法
    // JDK 8 哈希扰动算法
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    
    • 高位异或:分散哈希冲突(解决低位相似 key 的碰撞)
  • 扩容机制
    • 默认初始容量 16,负载因子 0.75
    • 扩容阈值 = 容量 × 负载因子
    • 扩容后容量翻倍,元素 rehash(JDK 8 优化:元素位置 = 原位置 OR 原位置+旧容量)
2. TreeMap
  • 红黑树核心特性
    • 自平衡二叉查找树(确保最坏情况 O(log n) 操作)
    • 节点规则:
      • 根节点为黑色
      • 红色节点的子节点必须为黑色
      • 任意节点到叶子的路径包含相同数量黑节点
  • 排序控制
    • 自然排序:Key 实现 Comparable 接口
    • 定制排序:构造时传入 Comparator
      TreeMap<String, Integer> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
      
3. LinkedHashMap
  • 数据结构扩展
    • 继承 HashMap,增加 双向链表 维护顺序
      static class Entry<K,V> extends HashMap.Node<K,V> {
          Entry<K,V> before, after; // 双向链表指针
      }
      
  • 顺序模式
    • 插入顺序(默认):按 put() 顺序记录
    • 访问顺序(LRU 基础):
      LinkedHashMap<String, Integer> lruCache = new LinkedHashMap(16, 0.75f, true);
      
      • 开启后,get()put() 已存在键会将其移到链表末尾
4. Hashtable
  • 遗留实现
    • HashMap 类似但全方法用 synchronized 修饰(性能差)
    • 初始容量 11,扩容:newCapacity = oldCapacity * 2 + 1
    • 无红黑树优化,冲突时纯链表解决

三、使用场景对比
场景推荐实现原因
高频键值存取(无顺序要求)HashMap最优的平均 O(1) 性能
需要键的自然或自定义排序TreeMap红黑树保证有序性,支持 firstKey()/subMap() 等导航操作
保留插入顺序或实现 LRU 缓存LinkedHashMap双向链表维护顺序,可直接用作缓存(需重写 removeEldestEntry()
线程安全环境(已过时)ConcurrentHashMapHashtable 不推荐!优先选分段锁的 ConcurrentHashMap(JDK 7+)
兼容遗留系统Hashtable仅用于兼容旧代码,新项目禁用

四、关键代码示例
1. LinkedHashMap 实现 LRU 缓存
final int MAX_ENTRIES = 100;
LinkedHashMap<Integer, String> lruCache = new LinkedHashMap(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES; // 容量超限时移除最旧条目
    }
};
2. TreeMap 范围查询
TreeMap<Integer, String> scores = new TreeMap<>();
scores.put(90, "Alice");
scores.put(85, "Bob");
scores.put(95, "Charlie");

// 查询 [85, 95) 区间的键
NavigableMap<Integer, String> range = scores.subMap(85, true, 95, false);
System.out.println(range); // 输出: {85=Bob, 90=Alice}

五、性能与线程安全对比
维度HashMapTreeMapLinkedHashMapHashtable
get/put 平均O(1)O(log n)O(1)O(1) (锁竞争劣化)
内存占用最低中 (树指针)较高 (双向链表)类似 HashMap
线程安全需外部同步需外部同步需外部同步内置锁 (高开销)
推荐锁方案Collections.synchronizedMap()ConcurrentHashMap同左同左直接弃用

六、演进与替代方案
  1. 取代 Hashtable
    • 使用 ConcurrentHashMap(分段锁/JDK 8+ CAS 优化)
  2. 排序需求升级
    • 需要并发排序时用 ConcurrentSkipListMap(跳表实现)
  3. 缓存场景
    • 直接使用 Guava CacheBuilder 或 Caffeine

设计启示LinkedHashMap 是扩展性的典范——通过继承 HashMap 复用哈希存储,仅增加 12 字节/节点的链表指针便实现了顺序维护。


总结:核心选择准则
  • 最高性能HashMap
  • 键排序TreeMap
  • 插入/访问顺序LinkedHashMap
  • 线程安全ConcurrentHashMap (永不选 Hashtable!)
  • 警惕 null 值需求TreeMap 禁 null key,Hashtable 全禁 null。

并发编程

1. Java中如何实现线程的同步?

在Java中,线程同步是为了防止多个线程同时访问共享资源,导致数据的不一致性。可以通过以下方式实现线程同步:

  • 使用 synchronized 关键字
    synchronized 用于修饰方法或者代码块,确保同一时间只有一个线程能够访问该方法或代码块。

    • 同步实例方法

      synchronized void method() {
          // 同步代码
      }
      
    • 同步静态方法

      synchronized static void staticMethod() {
          // 同步代码
      }
      
    • 同步代码块

      void method() {
          synchronized(this) {
              // 同步代码
          }
      }
      
  • 使用 Lock 接口
    Lock 提供了比 synchronized 更加灵活的锁机制,可以精确控制锁的获取和释放。例如使用 ReentrantLock

    Lock lock = new ReentrantLock();
    lock.lock();
    try {
        // 代码块
    } finally {
        lock.unlock();
    }
    

2. 什么是死锁(Deadlock),如何避免死锁?

  • 死锁是指两个或多个线程在执行过程中,由于竞争资源而造成的一种相互等待的状态,导致这些线程永远无法再继续执行。

    死锁的必要条件

    1. 互斥条件:每个资源只能被一个线程占用。
    2. 请求与保持条件:线程已经占有一个资源,但又请求其他线程持有的资源。
    3. 不剥夺条件:已经获得的资源在使用完之前不能被其他线程强行剥夺。
    4. 循环等待条件:多个线程之间形成了一个循环等待资源的链。

    避免死锁的策略

    1. 避免嵌套锁:尽量避免多个锁的嵌套。
    2. 锁的顺序:按照一定的顺序获取资源,避免循环等待。
    3. 使用定时锁:通过 tryLock() 来设置锁的超时时间,避免线程长期等待。
    4. 避免资源饥饿:通过公平锁(例如 ReentrantLock 的公平模式)避免资源饥饿现象。

3. 解释一下Java中的synchronized关键字

  • synchronized 是Java中的一个关键字,用于控制访问共享资源的线程同步。它确保同一时刻只有一个线程能够执行同步的代码块或方法,从而避免多个线程并发操作共享资源导致数据不一致的情况。

    用法

    • 同步实例方法:确保同一时间只能有一个线程访问该实例方法。

      synchronized void method() {
          // 同步代码
      }
      
    • 同步静态方法:确保同一时间只能有一个线程访问该类的静态方法。

      synchronized static void staticMethod() {
          // 同步代码
      }
      
    • 同步代码块:锁定一个特定对象。

      void method() {
          synchronized(this) {
              // 同步代码
          }
      }
      

    特点

    • synchronized 是一种隐式锁,自动加锁和释放锁。
    • 线程在获取锁时,其他线程只能等待。
    • 可以通过对象的 monitor 来管理锁的获取和释放。

4. 什么是线程池?如何使用线程池?

  • 线程池 是一组线程的集合,用于执行提交的任务。线程池可以有效地减少线程的创建和销毁开销,提高性能,避免线程频繁创建导致的资源浪费。

    如何使用线程池
    Java提供了 Executor 框架来简化线程池的使用,最常用的是 ExecutorService 接口和 Executors 工厂类。

    ExecutorService executor = Executors.newFixedThreadPool(10);  // 创建一个固定大小的线程池
    
    executor.submit(new Runnable() {
        public void run() {
            // 任务代码
        }
    });
    
    executor.shutdown();  // 关闭线程池
    

    常见的线程池类型

    • newFixedThreadPool(int nThreads):固定大小的线程池。
    • newCachedThreadPool():根据需要创建新线程,空闲线程会在60秒后被回收。
    • newSingleThreadExecutor():单线程池,只有一个线程处理任务。
    • newScheduledThreadPool(int corePoolSize):支持定时任务的线程池。

5. Java中如何实现并发控制?

并发控制是通过多线程访问共享资源时对资源的保护,确保线程安全。Java提供了以下方式来控制并发:

  • 使用 synchronized 关键字:确保同一时刻只有一个线程能够访问某段代码。
  • 使用 Lock 接口:提供更高效的并发控制,可以手动控制锁的获取和释放,常用的实现类有 ReentrantLock
  • 使用并发集合类:Java并发包(java.util.concurrent)提供了一些线程安全的集合类,如 ConcurrentHashMapCopyOnWriteArrayList 等。
  • 使用 volatile 关键字:保证变量在多线程环境下的可见性。
  • 使用 Atomic:提供原子性操作,确保线程安全。

6. Java中的volatile关键字的作用是什么?

volatile 是一种轻量级的同步机制,确保多个线程访问变量时,始终读取最新的值。它有以下几个特点:

  • 可见性:保证当一个线程修改了 volatile 变量的值,其他线程能立即看到修改后的值。

  • 禁止指令重排:防止对 volatile 变量的操作被指令重排。

    注意

    • volatile 仅保证可见性,不保证原子性,因此不能代替 synchronized 来进行复杂的同步操作。

    示例:

    private volatile boolean flag = false;
    

7. 什么是Atomic类,它有什么作用?

Atomic 类是Java中的一组原子操作类,位于 java.util.concurrent.atomic 包下。它们提供了无锁的原子操作,使得对单个变量的操作是线程安全的。

  • 常见的 Atomic

    • AtomicInteger
    • AtomicLong
    • AtomicBoolean
    • AtomicReference<T>

    这些类提供了 get(), set(), incrementAndGet(), compareAndSet() 等方法,来实现线程安全的操作。

    示例:

    AtomicInteger atomicInt = new AtomicInteger(0);
    atomicInt.incrementAndGet();  // 原子性递增
    

8. 什么是CountDownLatch和CyclicBarrier,它们的应用场景是什么?

  • CountDownLatch:是一个同步辅助类,允许一个或多个线程等待直到在其他线程中执行的一组操作完成。常用于任务的同步等待。

    • 应用场景:可以用于等待多个线程完成任务后再继续执行主线程。

    示例:

    CountDownLatch latch = new CountDownLatch(3);  // 等待3个线程
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            // 执行任务
            latch.countDown();  // 任务完成,计数减一
        }).start();
    }
    latch.await();  // 等待计数变为0
    
  • CyclicBarrier:是一个同步辅助类,允许一组线程相互等待,直到所有线程都到达某个公共屏障点,然后继续执行。CyclicBarrier 可以重复使用,适合多轮迭代。

    • 应用场景:适用于多个线程按阶段执行,确保所有线程到达某个阶段后才继续执行。

    示例:

    CyclicBarrier barrier = new CyclicBarrier(3);  // 3个线程达到屏障
    for (int i = 0; i < 3; i++) {
        new Thread(() -> {
            // 执行任务
            try {
                barrier.await();  // 等待其他线程
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();
    }
    

9. Java中的线程状态有哪些?

Java线程有7种状态:

  1. 新建状态(New):线程创建后,尚未开始执行时处于此状态。
  2. 就绪状态(Runnable):线程可以执行,等待CPU分配时间片。
  3. 运行状态(Running)

:线程正在执行代码。
4. 阻塞状态(Blocked):线程因为某种原因(如等待锁)被挂起,等待重新获取锁。
5. 等待状态(Waiting):线程在没有时间限制的情况下等待其他线程的通知(如 Object.wait())。
6. 定时等待状态(Timed Waiting):线程在指定时间内等待(如 Thread.sleep())。
7. 死亡状态(Terminated):线程执行完毕或被强制停止。


设计模式

1. 什么是设计模式?为什么要使用设计模式?

设计模式(Design Pattern)是软件开发过程中,经过反复实践总结出来的经验和最佳实践。它们提供了在特定情境下解决常见问题的解决方案。设计模式并不是具体的代码,而是描述了如何组织和创建代码结构,如何处理不同对象之间的关系等。

使用设计模式的原因

  • 提高代码的可维护性:设计模式帮助开发者写出更清晰、更易于理解和修改的代码。
  • 增加代码的复用性:设计模式能够帮助开发者根据特定的需求复用已有的解决方案。
  • 解决复杂性问题:面对复杂的系统时,设计模式通过抽象和分层结构,帮助解决系统间的复杂关系。
  • 促进代码的扩展性:使用设计模式可以让系统在需要时更容易进行扩展,而不破坏现有的代码结构。

2. 什么是单例模式,怎么实现线程安全的单例模式?

单例模式(Singleton Pattern)是设计模式中的一种,它确保一个类在内存中只有一个实例,并提供一个全局访问点来获取该实例。

线程安全的单例模式实现

  • 饿汉式(线程安全)
    在类加载时就初始化单例对象,确保线程安全。缺点是没有延迟加载,可能浪费内存。

    public class Singleton {
        private static final Singleton instance = new Singleton();
        
        private Singleton() {}
        
        public static Singleton getInstance() {
            return instance;
        }
    }
    
  • 懒汉式(线程不安全)
    通过在 getInstance() 方法中创建实例,只有在需要时才初始化对象。这个实现不具备线程安全性。

    public class Singleton {
        private static Singleton instance;
        
        private Singleton() {}
        
        public static Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    
  • 懒汉式(线程安全)
    使用 synchronized 关键字来确保多线程访问时只有一个线程能创建实例,避免线程不安全问题。

    public class Singleton {
        private static Singleton instance;
        
        private Singleton() {}
        
        public static synchronized Singleton getInstance() {
            if (instance == null) {
                instance = new Singleton();
            }
            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;
        }
    }
    

3. 什么是工厂模式,如何实现工厂模式?

工厂模式(Factory Pattern)是一种创建型设计模式,提供了创建对象的接口,但由子类决定实例化哪一个类。工厂模式使得代码更灵活,能够在不修改客户端代码的情况下扩展新的产品。

简单工厂模式
工厂类根据传入的信息决定创建哪种类型的对象。

public class AnimalFactory {
    public static Animal createAnimal(String type) {
        if ("dog".equalsIgnoreCase(type)) {
            return new Dog();
        } else if ("cat".equalsIgnoreCase(type)) {
            return new Cat();
        }
        return null;
    }
}

抽象工厂模式
在抽象工厂模式中,工厂方法会返回一个工厂接口的实现类,而这个工厂接口实现具体产品的创建。

public interface Animal {
    void sound();
}

public class Dog implements Animal {
    public void sound() {
        System.out.println("Woof");
    }
}

public class Cat implements Animal {
    public void sound() {
        System.out.println("Meow");
    }
}

public interface AnimalFactory {
    Animal createAnimal();
}

public class DogFactory implements AnimalFactory {
    public Animal createAnimal() {
        return new Dog();
    }
}

public class CatFactory implements AnimalFactory {
    public Animal createAnimal() {
        return new Cat();
    }
}

4. 什么是观察者模式?它的应用场景是什么?

观察者模式(Observer Pattern)是一种行为型设计模式,定义了对象间的一对多依赖关系,使得当一个对象的状态发生改变时,所有依赖于它的对象都会收到通知并自动更新。观察者模式通常用于实现事件处理系统、订阅/发布系统等。

应用场景

  • 事件处理系统:比如GUI应用中的按钮点击事件。
  • MVC架构:在模型-视图-控制器模式中,模型(数据)更新时通知视图更新。
// Subject
public class Subject {
    private List<Observer> observers = new ArrayList<>();
    
    public void addObserver(Observer observer) {
        observers.add(observer);
    }
    
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }
    
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }
}

// Observer
public interface Observer {
    void update();
}

public class ConcreteObserver implements Observer {
    public void update() {
        System.out.println("State updated!");
    }
}

5. 什么是策略模式?

策略模式(Strategy Pattern)是一种行为型设计模式,定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式使得算法可独立于使用它的客户端变化。

应用场景

  • 支付方式选择:在支付系统中,可以根据用户选择的支付方式(信用卡、支付宝、微信支付等)来执行不同的支付算法。
// Strategy interface
public interface PaymentStrategy {
    void pay(int amount);
}

// Concrete strategies
public class CreditCardPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card.");
    }
}

public class PayPalPayment implements PaymentStrategy {
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal.");
    }
}

// Context
public class ShoppingCart {
    private PaymentStrategy paymentStrategy;
    
    public ShoppingCart(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }
    
    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

6. 什么是代理模式?它的应用场景是什么?

代理模式(Proxy Pattern)是一种结构型设计模式,提供一个代理对象来控制对另一个对象的访问。代理对象和真实对象实现相同的接口,代理对象通过内部实现访问真实对象的功能。

应用场景

  • 虚拟代理:在需要懒加载或需要延迟实例化的情况下使用,如图片加载等。
  • 远程代理:在远程调用的系统中,使用代理模式来处理远程对象的访问。
  • 安全代理:用于控制对真实对象的访问,只有通过代理才能访问真实对象。
// Subject
public interface RealSubject {
    void request();
}

// RealSubject implementation
public class RealSubjectImpl implements RealSubject {
    public void request() {
        System.out.println("Real subject request.");
    }
}

// Proxy
public class Proxy implements RealSubject {
    private RealSubjectImpl realSubject;
    
    public void request() {
        if (realSubject == null) {
            realSubject = new RealSubjectImpl();
        }
        realSubject.request();
    }
}

7. 什么是模板方法模式?

模板方法模式(Template Method Pattern)是一种行为型设计模式,它定义了一个操作中的算法骨架,并将一些步骤的实现延迟到子类中。模板方法模式允许子类在不改变算法结构的情况下,重新定义算法的某些特定步骤。

应用场景

  • 框架设计:在开发框架时,常常会用模板方法来定义框架的执行流程,而具体的细节留给用户来实现。
  • 数据处理流程:如在数据清洗或任务调度中,整个流程是固定的,而某些步骤可以根据不同的需求进行定制。
// Abstract class with template method
public abstract class AbstractClass {
    public void templateMethod() {
        step1();
        step2();
        step3();
    }
    
    protected abstract void step1();
    protected abstract void step2();
    
    // Concrete step
    protected void step3() {
        System.out.println("Step 3 completed.");
    }
}

// Concrete class implementation
public class ConcreteClass extends AbstractClass {
    protected void step1() {
        System.out.println("Step 1 completed.");
    }

    protected void step2() {
        System.out.println("Step 2 completed.");
    }
}

Spring Boot

⚙️ 一、Spring Boot 核心概念与优势

  1. 什么是 Spring Boot?其核心优势是什么?
    Spring Boot 是基于 Spring 框架的快速开发脚手架,通过 约定大于配置 原则简化项目搭建。核心优势包括:
    • 自动配置:根据依赖自动配置 Bean(如添加 spring-boot-starter-web 自动配置 Tomcat 和 Spring MVC)。
    • 内嵌容器:无需部署 WAR 包,直接打包为可执行 JAR(通过 java -jar 启动)。
    • 起步依赖(Starters):聚合常用依赖(如 spring-boot-starter-data-jpa 包含 JPA + Hibernate),解决版本冲突。
    • 生产就绪:集成 Actuator 提供健康检查、指标监控等端点。
      面试考察点:能否对比传统 Spring MVC 的繁琐配置,说明 Spring Boot 如何提升开发效率。

🔧 二、自动配置原理(最高频考点)

  1. Spring Boot 自动配置是如何实现的?
    核心流程如下:
    1. 启动类注解@SpringBootApplication 包含 @EnableAutoConfiguration
    2. 加载自动配置类:通过 SpringFactoriesLoader 加载 META-INF/spring.factories 中注册的配置类(如 SpringBootWebMvcConfiguration)。
    3. 条件注解过滤
      • @ConditionalOnClass:类路径存在指定类时生效(如存在 Servlet.class 才配置 Tomcat)。
      • @ConditionalOnMissingBean:容器中无自定义 Bean 时启用默认配置。
        伪代码示例
    @AutoConfiguration
    @ConditionalOnClass({ Servlet.class, Tomcat.class })
    public class EmbeddedWebServerAutoConfiguration {
        @Bean
        @ConditionalOnMissingBean
        public TomcatServletWebServerFactory tomcatFactory() {
            return new TomcatServletWebServerFactory();
        }
    }
    
    面试考察点:是否理解条件注解的作用机制及如何自定义覆盖默认配置。

🚀 三、启动过程详解

  1. 描述 Spring Boot 应用启动流程
    关键步骤:
    1. 初始化 SpringApplication
      • 加载 SpringFactories 中的 ApplicationContextInitializerApplicationListener
    2. 执行 run() 方法
      • 创建 ApplicationContext(默认 AnnotationConfigServletWebServerApplicationContext)。
      • 加载自动配置类并实例化 Bean。
      • 启动内嵌 Web 服务器(如 Tomcat)。
        面试考察点:是否清楚上下文初始化与 Bean 加载的时序,以及如何扩展启动过程(如自定义 ApplicationRunner)。

📌 四、核心注解解析

  1. @SpringBootApplication 的作用是什么?
    该注解是组合注解,包含:
    • @SpringBootConfiguration:标记当前类为配置类。
    • @EnableAutoConfiguration:启用自动配置。
    • @ComponentScan:扫描当前包及子包的 @Component 组件。
      示例
    @SpringBootApplication // 等同于以上三个注解
    public class DemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    

⚖️ 五、配置系统与优先级

  1. 配置文件的加载顺序与优先级
    Spring Boot 支持多环境配置,优先级从高到低:
    1. 命令行参数(--server.port=8081)。
    2. 外部配置文件(file:./config/application.yml)。
    3. 类路径配置文件(classpath:/config/application.yml)。
      YAML vs Properties
    • YAML 支持结构化配置(如多环境 Profile),更适合复杂配置。
      面试考察点:多环境配置管理(如 application-dev.yml)及敏感信息加密方案(使用 jasypt)。

📦 六、内嵌容器与可执行 JAR

  1. Spring Boot 如何支持内嵌 Tomcat?
    • 原理:通过 spring-boot-starter-web 引入 tomcat-embed-core,启动时由 TomcatServletWebServerFactory 创建 Web 服务器。
    • 可执行 JAR 结构
      • BOOT-INF/classes:应用类文件。
      • BOOT-INF/lib:依赖库。
      • org/springframework/boot/loader:Spring Boot 类加载器(JarLauncher)。
        切换容器:在 POM 中排除 Tomcat 并引入 Jetty 依赖:
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jetty</artifactId>
    </dependency>
    

📊 七、生产就绪特性(Actuator)

  1. Spring Boot Actuator 的作用与端点安全
    • 核心端点
      • /health:应用健康状态。
      • /metrics:JVM 指标(内存、线程等)。
      • /env:环境变量。
    • 安全管理
      • 默认仅暴露 /health/info
      • 禁用安全性:management.security.enabled=false(仅限内网环境)。
        面试考察点:如何通过自定义端点暴露应用内部状态(如数据库连接池指标)。

🔄 八、Spring Boot 与 Spring 框架的关系

  1. Spring Boot 和 Spring 的区别?
    • Spring:基础框架,需手动配置 Bean、事务、MVC 等。
    • Spring Boot:基于 Spring 的脚手架,通过自动配置和 Starter 简化开发,核心仍是 Spring IOC/AOP。
      类比:Spring 是发动机,Spring Boot 是装好发动机的汽车。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值