基础知识
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:无具体取值范围,只有两个值:
true或false
2. 自动装箱和拆箱
-
自动装箱(Autoboxing):Java会自动将基本数据类型转换为其对应的包装类。例如,
int自动转换为Integer,char自动转换为Character等。例如:Integer i = 10; // 自动装箱 -
拆箱(Unboxing):Java会自动将包装类转换为其对应的基本数据类型。例如,
Integer自动转换为int,Character自动转换为char等。例如:int j = i; // 拆箱
3. Java中的String和StringBuilder的区别
-
String:
- 不可变(immutable),每次修改都会生成新的对象。
- 使用字符串常量池优化内存使用,多个相同的字符串引用同一个对象。
- 性能较低,特别是多次修改字符串的情况。
-
StringBuilder:
- 可变(mutable),可以对字符串进行修改而不生成新的对象。
- 适用于多次修改字符串的场景,性能更好。
- 线程不安全,若在多线程环境下使用需额外处理。
4. Java中的equals和hashCode方法的关系
equals()方法:用于比较两个对象的内容是否相等。默认情况下,equals()比较的是对象的引用(即内存地址),而不是对象的内容。hashCode()方法:返回对象的哈希码,用于对象的快速查找(如HashMap、HashSet等集合类)。
关系:
- 如果两个对象通过
equals()方法被认为是相等的,那么它们的hashCode()也必须相等。 - 反过来,两个对象的
hashCode()不相等,那么它们通过equals()方法肯定不相等。
5. 如何在Java中创建一个线程?使用Runnable和Thread的不同
创建线程有两种方式:
-
继承Thread类:通过继承
Thread类并重写run()方法。class MyThread extends Thread { public void run() { System.out.println("Thread is running"); } } MyThread t = new MyThread(); t.start(); // 启动线程 -
实现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中的异常分为两大类:
-
检查异常(Checked Exception):
- 继承自
Exception类,程序必须显式处理(通过try-catch或throws)。 - 如:
IOException,SQLException。
- 继承自
-
非检查异常(Unchecked Exception):
- 继承自
RuntimeException类,程序不需要强制处理。 - 如:
NullPointerException,ArrayIndexOutOfBoundsException。
- 继承自
区别:
- 检查异常通常是程序无法预料的异常,必须处理;而非检查异常一般是编程错误或可以避免的情况,通常不强制要求处理。
9. 解释Java中的try-catch-finally机制,finally块一定会执行吗?
try块:用于执行可能抛出异常的代码。catch块:用于捕获并处理异常。finally块:无论是否发生异常,都会执行,用于资源清理(如关闭文件、数据库连接等)。
finally块一定会执行吗?
- 在正常情况下,
finally块会执行,但如果在try或catch块中使用了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:有序且可重复的集合,元素按插入顺序排列,常见实现类包括
ArrayList、LinkedList和Vector。 - Set:不允许重复的集合,元素不按顺序排列。常见实现类有
HashSet、LinkedHashSet和TreeSet。 - Queue:先进先出(FIFO)顺序的集合,常见实现类有
LinkedList(也实现了 List 接口)和PriorityQueue。 - Map:键值对集合,常见实现类包括
HashMap、TreeMap和LinkedHashMap。 - SortedSet 和 SortedMap:这些接口提供了对集合元素的排序操作,常见实现类包括
TreeSet和TreeMap。
2. ArrayList和LinkedList有什么区别?
ArrayList 和 LinkedList 都实现了 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中都会被自动去重。 - 元素的顺序不一定是固定的。
- 常见实现类包括
HashSet、LinkedHashSet和TreeSet。
- 不允许重复的元素,任何重复的元素在
-
List:
- 允许重复的元素。
- 保证元素的插入顺序。
- 常见实现类包括
ArrayList、LinkedList和Vector。
5. 什么是Map接口,HashMap和Hashtable的区别是什么?
-
Map接口:
- 用于存储键值对数据,每个元素包含一个唯一的键和一个值。Map 不继承 Collection 接口,它本身是一个独立的接口。
- 常见实现类包括
HashMap、TreeMap、LinkedHashMap和Hashtable。
-
HashMap 和 Hashtable 的区别:
- 线程安全:
Hashtable是线程安全的,而HashMap是不安全的,HashMap在并发环境下需要额外同步。 null值:HashMap允许一个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是什么?它有什么特点?
-
LinkedHashMap 是
HashMap的一个子类,具有以下特点:- 它维护了元素的插入顺序或访问顺序(如果构造时使用了
accessOrder标志)。 - 和
HashMap一样,提供常数时间的查找、插入和删除操作,但它还维护了一个双向链表,按照元素的插入顺序或访问顺序排列元素。
- 它维护了元素的插入顺序或访问顺序(如果构造时使用了
-
特性:
- 插入顺序:默认情况下,
LinkedHashMap保持元素的插入顺序。 - 访问顺序:通过设置构造参数为
true,LinkedHashMap可以根据元素的访问顺序排列。
- 插入顺序:默认情况下,
示例:
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.HashMap、TreeMap、LinkedHashMap 和 Hashtable 的深度辨析
一、核心特性总览
| 特性 | HashMap | TreeMap | LinkedHashMap | Hashtable |
|---|---|---|---|---|
| 数据结构 | 数组+链表/红黑树 | 红黑树 | 数组+链表/红黑树+双向链表 | 数组+链表 |
| 排序 | 无序 | Key 自然排序/自定义 | 插入顺序/访问顺序 | 无序 |
| 线程安全 | ❌ | ❌ | ❌ | ✅ (全表锁) |
| Null 支持 | Key/Value 均可为 null | Key 不能为 null | Key/Value 均可为 null | Key/Value 均不能为 null |
| 性能 | O(1) 查找/插入 (平均) | O(log n) 操作 | O(1) 查找/插入 (平均) | O(1) 操作 (锁竞争影响) |
| 继承关系 | AbstractMap | NavigableMap | HashMap 子类 | 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接口 - 定制排序:构造时传入
ComparatorTreeMap<String, Integer> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
- 自然排序:Key 实现
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()) |
| 线程安全环境(已过时) | ConcurrentHashMap | Hashtable 不推荐!优先选分段锁的 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}
五、性能与线程安全对比
| 维度 | HashMap | TreeMap | LinkedHashMap | Hashtable |
|---|---|---|---|---|
| get/put 平均 | O(1) | O(log n) | O(1) | O(1) (锁竞争劣化) |
| 内存占用 | 最低 | 中 (树指针) | 较高 (双向链表) | 类似 HashMap |
| 线程安全 | 需外部同步 | 需外部同步 | 需外部同步 | 内置锁 (高开销) |
| 推荐锁方案 | Collections.synchronizedMap() 或 ConcurrentHashMap | 同左 | 同左 | 直接弃用 |
六、演进与替代方案
- 取代
Hashtable- 使用
ConcurrentHashMap(分段锁/JDK 8+ CAS 优化)
- 使用
- 排序需求升级
- 需要并发排序时用
ConcurrentSkipListMap(跳表实现)
- 需要并发排序时用
- 缓存场景
- 直接使用 Guava
CacheBuilder或 Caffeine
- 直接使用 Guava
设计启示:
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),如何避免死锁?
-
死锁是指两个或多个线程在执行过程中,由于竞争资源而造成的一种相互等待的状态,导致这些线程永远无法再继续执行。
死锁的必要条件:
- 互斥条件:每个资源只能被一个线程占用。
- 请求与保持条件:线程已经占有一个资源,但又请求其他线程持有的资源。
- 不剥夺条件:已经获得的资源在使用完之前不能被其他线程强行剥夺。
- 循环等待条件:多个线程之间形成了一个循环等待资源的链。
避免死锁的策略:
- 避免嵌套锁:尽量避免多个锁的嵌套。
- 锁的顺序:按照一定的顺序获取资源,避免循环等待。
- 使用定时锁:通过
tryLock()来设置锁的超时时间,避免线程长期等待。 - 避免资源饥饿:通过公平锁(例如
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)提供了一些线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList等。 - 使用
volatile关键字:保证变量在多线程环境下的可见性。 - 使用
Atomic类:提供原子性操作,确保线程安全。
6. Java中的volatile关键字的作用是什么?
volatile 是一种轻量级的同步机制,确保多个线程访问变量时,始终读取最新的值。它有以下几个特点:
-
可见性:保证当一个线程修改了
volatile变量的值,其他线程能立即看到修改后的值。 -
禁止指令重排:防止对
volatile变量的操作被指令重排。注意:
volatile仅保证可见性,不保证原子性,因此不能代替synchronized来进行复杂的同步操作。
示例:
private volatile boolean flag = false;
7. 什么是Atomic类,它有什么作用?
Atomic 类是Java中的一组原子操作类,位于 java.util.concurrent.atomic 包下。它们提供了无锁的原子操作,使得对单个变量的操作是线程安全的。
-
常见的
Atomic类:AtomicIntegerAtomicLongAtomicBooleanAtomicReference<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种状态:
- 新建状态(New):线程创建后,尚未开始执行时处于此状态。
- 就绪状态(Runnable):线程可以执行,等待CPU分配时间片。
- 运行状态(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 核心概念与优势
- 什么是 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 如何提升开发效率。
- 自动配置:根据依赖自动配置 Bean(如添加
🔧 二、自动配置原理(最高频考点)
- Spring Boot 自动配置是如何实现的?
核心流程如下:- 启动类注解:
@SpringBootApplication包含@EnableAutoConfiguration。 - 加载自动配置类:通过
SpringFactoriesLoader加载META-INF/spring.factories中注册的配置类(如SpringBootWebMvcConfiguration)。 - 条件注解过滤:
@ConditionalOnClass:类路径存在指定类时生效(如存在Servlet.class才配置 Tomcat)。@ConditionalOnMissingBean:容器中无自定义 Bean 时启用默认配置。
伪代码示例:
面试考察点:是否理解条件注解的作用机制及如何自定义覆盖默认配置。@AutoConfiguration @ConditionalOnClass({ Servlet.class, Tomcat.class }) public class EmbeddedWebServerAutoConfiguration { @Bean @ConditionalOnMissingBean public TomcatServletWebServerFactory tomcatFactory() { return new TomcatServletWebServerFactory(); } } - 启动类注解:
🚀 三、启动过程详解
- 描述 Spring Boot 应用启动流程
关键步骤:- 初始化
SpringApplication:- 加载
SpringFactories中的ApplicationContextInitializer和ApplicationListener。
- 加载
- 执行
run()方法:- 创建
ApplicationContext(默认AnnotationConfigServletWebServerApplicationContext)。 - 加载自动配置类并实例化 Bean。
- 启动内嵌 Web 服务器(如 Tomcat)。
面试考察点:是否清楚上下文初始化与 Bean 加载的时序,以及如何扩展启动过程(如自定义ApplicationRunner)。
- 创建
- 初始化
📌 四、核心注解解析
@SpringBootApplication的作用是什么?
该注解是组合注解,包含:@SpringBootConfiguration:标记当前类为配置类。@EnableAutoConfiguration:启用自动配置。@ComponentScan:扫描当前包及子包的@Component组件。
示例:
@SpringBootApplication // 等同于以上三个注解 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
⚖️ 五、配置系统与优先级
- 配置文件的加载顺序与优先级
Spring Boot 支持多环境配置,优先级从高到低:- 命令行参数(
--server.port=8081)。 - 外部配置文件(
file:./config/application.yml)。 - 类路径配置文件(
classpath:/config/application.yml)。
YAML vs Properties:
- YAML 支持结构化配置(如多环境 Profile),更适合复杂配置。
面试考察点:多环境配置管理(如application-dev.yml)及敏感信息加密方案(使用jasypt)。
- 命令行参数(
📦 六、内嵌容器与可执行 JAR
- 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)
- Spring Boot Actuator 的作用与端点安全
- 核心端点:
/health:应用健康状态。/metrics:JVM 指标(内存、线程等)。/env:环境变量。
- 安全管理:
- 默认仅暴露
/health和/info。 - 禁用安全性:
management.security.enabled=false(仅限内网环境)。
面试考察点:如何通过自定义端点暴露应用内部状态(如数据库连接池指标)。
- 默认仅暴露
- 核心端点:
🔄 八、Spring Boot 与 Spring 框架的关系
- Spring Boot 和 Spring 的区别?
- Spring:基础框架,需手动配置 Bean、事务、MVC 等。
- Spring Boot:基于 Spring 的脚手架,通过自动配置和 Starter 简化开发,核心仍是 Spring IOC/AOP。
类比:Spring 是发动机,Spring Boot 是装好发动机的汽车。
Java编程基础与高级特性解析
553

被折叠的 条评论
为什么被折叠?



