高频快问快答
阅读提示
这页适合“面试前 30 分钟”查漏补缺。
用法建议:先看题目,自己口述 20~40 秒,再对照参考回答补关键术语。
本文语言与线程 API 以 Java SE 21 为基线;构造器语法另注明 Java 25 的变化。锁实现特指 HotSpot,并区分历史版本与较新版本,不能作为所有 JVM 的规范。
使用方式
- 每次抽 5 题,限时回答。
- 对回答卡顿的题,加上自己的项目例子再复述一遍。
- 对“线程、锁、反射、关键字”这四类题优先复盘。
Java有哪些数据类型

访问修饰符 public,private,protected,以及不写(默认)时的区别
- private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
- default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。 使用对象:类、接口、变量、方法。
- protected : 同包内可访问;跨包时仅在子类代码内按规则访问,访问实例成员还要求接收者的编译时类型为该子类或其子类型。使用对象:变量、方法。 注意:不能修饰类(外部类)。
- public : 在声明类型可访问的前提下可访问;Java 9+ 跨模块还受模块可读性和包导出限制。使用对象:类、接口、变量、方法
依据:JLS 6.6:访问控制。

final finally finalize区别
final修饰类表示不能继承,修饰方法表示不能重写,修饰变量表示只能赋值一次。final引用不能重新指向其他对象,但对象内部状态仍可能改变;并非所有 final 变量都是编译期常量。finally在 try/catch 正常或异常退出时执行,但 JVM 退出、进程终止等情况下不保证执行。不要在 finally 中 return 或抛出新异常,否则可能覆盖原结果或异常;关闭AutoCloseable资源优先使用 try-with-resources。finalize()是 Object 的终结方法,执行时机与是否执行都不可靠,System.gc()只是请求 GC,不保证回收或调用终结方法。终结机制不是判断对象可回收的必要步骤。它从 Java 9 起弃用,Java 18 起标记为待移除,不应承担资源释放职责。
依据:Object.finalize(Java 21)、JLS 14.20.2:finally 的执行。
this与super的区别
this是当前对象的引用,可用this.field区分成员变量与同名参数;Java 不把它定义为可运算的指针。super.field、super.method()用来访问父类中可访问的成员,例如被隐藏的字段或被重写的方法;super 不是另一个父类对象,也不能访问父类 private 成员。this(...)调用本类的其他构造器,super(...)调用直接父类构造器。一个构造器不能同时显式调用二者,但完全可以同时出现this.field与super.method()。this 构造器调用链不能循环,最终会调用父类构造器。- Java 21 及以前的常规语法要求显式构造器调用是构造器体的第一条语句;省略时通常隐式调用
super(),因此父类需要有可访问的无参构造器。 - Java 25 正式支持灵活构造器体,允许在显式构造器调用前写受限制的代码;不能因此随意访问尚未完成构造的对象。不要把“第一条语句”作为所有版本的永久规则。
this、super不能在静态上下文中指代当前实例。
static存在的主要意义
static字段和方法属于类,可通过类名访问,不必先创建实例。静态方法没有当前实例,不能直接访问实例字段。- 静态字段初始化器与静态代码块在类初始化阶段按文本顺序执行,不能等同于“类一加载就执行”。初始化会由首次主动使用等条件触发,JVM 对初始化过程进行同步;不同类加载器定义的同名类各有自己的静态状态。
- 静态代码块适合一次性初始化,但不天然提升性能:耗时初始化会延迟首次使用,初始化失败也会影响后续访问。
面向对象五大基本原则是什么
- 单一职责原则SRP(Single Responsibility Principle) :类的功能要单一,不能包罗万象,跟杂货铺似的。
- 开放封闭原则OCP(Open-Close Principle) :一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。
- 里式替换原则LSP(the Liskov Substitution Principle LSP) : 子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~
- 依赖倒置原则DIP(the Dependency Inversion Principle DIP) :高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的抽象是中国人,而不是你是xx村的。
- 接口分离原则ISP(the Interface Segregation Principle ISP) :设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。
重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
- 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
- 重载:可以发生在同一类中,也可以涉及继承的方法;方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
- 重写:发生在父子类中,方法名、参数列表必须相同,引用返回类型可协变(相同或为子类型),基本类型或 void 返回类型须一致;不能扩大受检异常范围,不能降低访问权限;如果父类方法访问修饰符为private则子类中就不是重写。
反射机制的应用场景有哪些?
- 反射是框架设计的灵魂。
- 在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射查询类型信息、创建对象和调用成员;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
- 举例:①旧式 JDBC 代码使用 Class.forName() 加载驱动;JDBC 4.0 起,正确提供服务声明的驱动可由 DriverManager 自动发现,通常无需显式调用(官方 API);②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
线程的sleep、wait、join、yield如何使用?
- sleep:暂停当前线程,不释放它已持有的监视器;到期后只是具备继续运行的条件,不保证立即得到 CPU。可被中断并抛出 InterruptedException。
- wait:调用者必须持有接收对象的监视器(例如处于该对象的 synchronized 块或同步方法中),否则抛出 IllegalMonitorStateException。它只释放该对象的监视器,不释放其他锁;通知、中断、超时或虚假唤醒都可能使等待结束。
- join:当前线程等待目标线程终止;带超时的 join 可能因超时返回,不能据此认定目标已经结束。等待也可被中断。
- yield:给调度器的提示,调度器可以忽略它;不保证让出 CPU,也不能用于线程同步。
notifyAll() 仅使等待线程有机会继续竞争监视器,不会立即把锁交给它。线程重新获得锁后必须再次检查条件;在同一监视器保护下修改条件和检查条件,才能避免检查与等待之间遗漏通知:
// Java 21:单次交付示例,两个方法由不同线程调用
final class Signal {
private boolean ready;
synchronized void awaitReady() throws InterruptedException {
while (!ready) { // 防止虚假唤醒,也防止醒来后条件已不满足
wait();
}
}
synchronized void markReady() {
ready = true;
notifyAll(); // 退出同步方法后,等待方才能取得该监视器
}
}start 与 run 区别 ?
功能不同:
start()方法是Thread类的一个方法,用于启动一个新线程,使得线程进入就绪状态,等待JVM调度执行。调用start()后,线程会异步执行run()方法中的代码。
run()方法则是线程的主体方法,包含了线程要执行的代码。但是,如果直接调用run()方法,它会在当前线程的上下文中同步执行,而不会创建新的线程。
执行效果:
调用start()会创建新线程,使线程可被调度执行;是否提升效率取决于任务和调度开销,同一个 Thread 只能 start 一次。
直接调用run()只是普通方法调用,该方法在调用线程上执行,本次调用不会新增并发执行路径。
代码示例:
package com.javatiaocao.threading.startrun;
public class ThreadExample extends Thread{
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(i + " 由 " + Thread.currentThread().getName() + " 输出");
try {
Thread.sleep(400); // 让线程休息一会儿
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
public static void main(String[] args) {
ThreadExample thread = new ThreadExample();
System.out.println("调用 run() 方法时:");
thread.run(); // 这行代码直接在主线程中执行 run 方法
// run() 同步返回时已经执行完毕,无需 sleep 等待。
System.out.println("调用 start() 方法时:");
thread.start(); // 这行代码会启动一个新的线程然后在新线程中执行 run 方法
// 在主线程中也进行输出
for (int i = 1; i <= 10; i++) {
System.out.println(i + " 由 " + Thread.currentThread().getName() + " 输出");
try {
Thread.sleep(400); // 主线程休息一会儿
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}什么是用户态,什么是内核态
用户态:
用户态是指程序在较低的特权级别下执行。
在用户态下,程序只能访问自己的内存空间和受限的系统资源,无法直接操作硬件设备或修改操作系统内核数据。
Java程序一般运行在用户态,通过Java虚拟机(JVM)来间接访问底层系统资源和硬件设备。
内核态:
内核态是指程序在较高的特权级别下执行。
在内核态下,程序拥有更高的权限,可以直接访问和操作操作系统的核心功能和硬件资源,如CPU、内存、硬盘等。
内核态下的程序可以执行特权操作,如修改内核数据结构、执行特权指令等。但同时,也需要承担相应的风险和责任。
总结来说,用户态和内核态的主要区别在于特权级别和资源访问能力。用户态下程序权限较低,受到限制;而内核态下程序具有更高的权限,可以直接访问和操作操作系统的核心资源。
各种类型的锁介绍
独占锁
- 独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。独占锁会串行化受保护的访问,即使多个线程都只读也不能同时持锁,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。
共享锁
- 共享模式允许多个线程同时持锁,例如 ReentrantReadWriteLock 的读锁;写锁仍独占,并与读锁互斥。共享/独占描述持锁方式,乐观/悲观描述并发控制策略,两者不是同一分类,共享读锁不等于乐观锁。
重量级锁
- HotSpot 可在竞争等情况下为对象关联 ObjectMonitor,必要时阻塞等待线程;不应把 synchronized 的每次加解锁都理解为操作系统互斥量调用或用户态/内核态切换,也不能笼统断言 synchronized 效率低。
轻量级锁
- HotSpot 使用快速加锁路径,在合适场景减少阻塞开销;竞争时可能先自旋,再膨胀为监视器。并非只要两个线程同时尝试获取锁就立即膨胀。
- 历史 HotSpot 的“无锁、偏向、轻量级、重量级”图是特定实现的概括,不是 Java 语言规范。也不要概括为对象整个生命周期内绝不降级:空闲监视器可以被回收(deflation)。
偏向锁
- 这是历史 HotSpot 针对同一线程反复获取同一锁的优化,以偏向线程信息减少重复原子操作;其他线程访问时可能触发撤销或重偏向,并不意味着原线程正在持有的锁可以被强行释放。
- JDK 15 已默认禁用偏向锁并弃用相关参数,因此不能用旧版默认启用的前提讲解 Java 21。JDK 23 的 HotSpot 又将默认加锁模式改为新的 Lightweight Locking,旧对象头/栈锁细节不能直接套用。
分段锁
- 分段锁是一种拆分竞争范围的设计思想。ConcurrentHashMap 的 Segment 方案是 JDK 7 的典型实现;JDK 8 起主要采用 CAS 与桶级 synchronized 等机制,不能再描述为固定 Segment 分段加锁(OpenJDK 8u 源码)。
依据:ReentrantReadWriteLock API、JDK 15 发布说明、JDK 23 发布说明。
锁的优化机制了解吗?
- 自旋与自适应自旋:短时间竞争时忙等可能比挂起再唤醒更划算,但会消耗 CPU。HotSpot 可依据竞争情况决定是否继续自旋,不能承诺固定自旋 10 次,也不应推荐旧稿中的 UseSpining、PreBlockSpin 参数用于现代 JDK。
- 锁消除:JIT 在证明同步对象不会被其他线程访问等条件下,可能消除不必要的同步;不是看到 synchronized 就一定保留,也不是凭业务直觉就能删除锁。
- 锁粗化:JIT 可能合并相邻、针对同一对象的加解锁以减少开销;扩大范围也可能增加竞争,不能说越粗越快。
- 快速路径与膨胀:具体结构和策略随 HotSpot 版本变化。Java 程序应依赖监视器的互斥与可见性语义,而不是某个对象头布局:对同一监视器的解锁 happens-before 后续成功加锁,保证锁内写入对后续持锁线程可见。
简洁示意:尝试快速加锁 → 竞争时可能自旋 → 必要时进入监视器等待 → 获锁后执行 → 解锁。这不是所有版本固定的状态升级图。
依据:JLS 17.4.5:happens-before、OpenJDK:ObjectMonitor 与 deflation。
原图保留作历史实现示意,不能据此断言 Java 21/23 的默认加锁路径:

