Java 5大核心关键字
5大关键字——对比表
| 关键字 | 核心作用 | 多线程相关 | 序列化相关 | 主要场景 |
|---|---|---|---|---|
final |
限制可变性(类、方法、变量) | 间接保证可见性 | ❌ | 常量、不可变类、安全设计 |
static |
类成员共享(变量、方法等) | ❌ | ❌ | 工具类、全局变量、类初始化 |
volatile |
可见性、禁止重排序 | ✅ | ❌ | 状态标记、DCL 单例 |
synchronized |
原子性、可见性、有序性 | ✅ | ❌ | 临界区同步、复合操作保护 |
transient |
跳过变量序列化 | ❌ | ✅ | 敏感数据、临时缓存、不可序列化字段 |
1. final 关键字
定义
final 用于限制类、方法或变量的可变性,表示“最终的、不可改变的”。
作用
- 修饰类:该类不可被继承(如
String、Math类)。 - 修饰方法:该方法不可被子类重写。
- 修饰变量:
- 基本类型变量:值不可修改。
- 引用类型变量:指向的对象地址不可修改(但对象内部状态可改变)。
使用场景
- 定义常量(如
public static final double PI = 3.14)。 - 防止类被继承或方法被重写(确保设计安全性)。
- 方法参数使用
final,避免参数被意外修改。
实现原理
- 编译期:
final变量会被编译器优化为“常量折叠”(直接替换值)。 - 运行期:JVM 会禁止对
final变量的二次赋值,并通过内存屏障确保final变量在构造函数中初始化后才被其他线程可见。
注意事项
final变量必须显式初始化(声明时、构造器或代码块中),否则编译报错。- 引用类型的
final仅限制引用地址,不限制对象内部状态(需结合其他机制保证不可变)。
2. static 关键字
定义
static 表示“静态的”,用于修饰类成员(变量、方法、代码块、内部类),使其属于类而非实例。
作用
- 修饰变量:静态变量(类变量)被所有实例共享,内存中仅存一份。
- 修饰方法:静态方法可直接通过类名调用,无需创建实例。
- 修饰代码块:静态代码块在类加载时执行一次,用于初始化类资源。
- 修饰内部类:静态内部类不依赖外部类实例,可直接创建。
使用场景
- 定义全局共享变量(如计数器、配置常量)。
- 工具类方法(如
Math.random()、Collections.sort())。 - 类初始化操作(如加载驱动、读取配置文件)。
实现原理
- 静态变量存储在方法区(JDK 8 后为元空间),随类加载而创建,类卸载而销毁。
- 静态方法在字节码层面通过
invokestatic指令调用,不依赖this引用。
注意事项
- 静态方法不能直接访问实例变量或实例方法(无
this引用),但实例方法可访问静态成员。 - 静态代码块仅执行一次,且按定义顺序执行。
3. volatile 关键字
定义
volatile 是多线程同步关键字,用于保证变量的可见性和禁止指令重排序,但不保证原子性。
作用
- 可见性:一个线程修改了
volatile变量,其他线程能立即看到最新值(避免变量被缓存在寄存器或本地内存中)。 - 禁止重排序:通过内存屏障禁止编译器和 CPU 对指令进行重排序(确保执行顺序与代码逻辑一致)。
使用场景
- 状态标记(如
boolean isRunning = true;,用于线程停止标记)。 - 双重检查锁定(DCL)实现单例模式(防止指令重排序导致的空指针异常)。
实现原理
- 可见性:通过缓存一致性协议(如 MESI),变量修改后立即刷新到主内存,其他线程嗅探到变化后失效本地缓存。
- 禁止重排序:在变量读写前后插入内存屏障(如 LoadLoad、StoreStore 屏障),限制指令重排范围。
注意事项
volatile不能替代synchronized,因为它不保证原子性(如count++这类复合操作仍需锁)。- 仅适用于“一个线程写、多个线程读”的场景,多线程写需额外同步。
4. synchronized 关键字
定义
synchronized 是 Java 中最常用的同步机制,用于保证多线程环境下的原子性、可见性和有序性。
作用
- 原子性:确保被修饰的方法或代码块同一时间只能被一个线程执行。
- 可见性:线程释放锁前,会将变量修改刷新到主内存;获取锁后,会从主内存读取最新值。
- 有序性:通过锁的“排他性”保证代码执行的有序性(禁止临界区内的重排序)。
使用场景
- 保护共享变量的复合操作(如
count++、转账操作)。 - 同步方法或代码块,确保多线程安全访问临界资源。
实现原理
- 对象头与 Mark Word:每个对象的对象头中存储锁状态(无锁、偏向锁、轻量级锁、重量级锁)。
- monitorenter/monitorexit 指令:编译后,同步代码块会插入这两个指令,用于获取和释放对象的监视器(Monitor)。
- 锁升级机制:JDK 6 后引入锁升级(偏向锁→轻量级锁→重量级锁),减少锁竞争带来的性能开销。
注意事项
- 锁的是对象而非代码:实例方法锁
this,静态方法锁类对象(Class),代码块锁指定对象。 - 避免锁粒度太大(影响性能)或太小(导致线程安全问题)。
- 支持可重入性:同一个线程可多次获取同一把锁(避免死锁)。
5. transient 关键字
定义
transient 用于修饰变量,表示该变量不需要被序列化(Java 序列化机制会忽略 transient 变量)。
作用
- 跳过敏感数据或临时数据的序列化(如密码、缓存计算结果)。
- 减少序列化后的文件大小,提升性能。
使用场景
- 缓存字段(如临时计算的中间结果,反序列化后可重新计算)。
- 敏感信息(如用户密码,避免序列化后泄露)。
- 不可序列化的字段(如
Thread、InputStream,需标记为transient避免报错)。
实现原理
- Java 序列化时,
ObjectOutputStream会检查字段是否被transient修饰,若是则跳过该字段的写入。 - 反序列化时,
transient变量会被初始化为默认值(引用类型为null,基本类型为0/false)。
注意事项
static变量天然不参与序列化(属于类状态,非对象状态),无需加transient。- 若需手动控制
transient变量的序列化,可实现Externalizable接口,重写writeExternal()和readExternal()方法。
面试高频考点
一、单个关键字 分阶高频考点
1. final 关键字
【基础必考题(初中级100%覆盖)】
- 简述final关键字的核心作用,分别说明修饰类、方法、变量时的语义
- final修饰基本类型变量和引用类型变量的核心区别
- 为什么String类要被定义为final?
- final和abstract能否一起使用?为什么?
- 匿名内部类访问外部局部变量,为什么变量必须加final?JDK8后为什么不用显式声明了?
【进阶深挖题(中高级高频)】
- final如何保证多线程下的可见性?和volatile的可见性实现有什么区别?
- 什么是不可变类?如何基于final实现一个线程安全的不可变类?
- final的编译期优化(常量折叠)是什么?有哪些限制条件?
- final方法能不能被重载?和重写的核心区别是什么?
- JVM对final字段有哪些重排序规则?为什么要禁止final字段在构造函数初始化完成前发生对象逸出?
【高频坑点题】
- final修饰的集合对象,能不能添加/删除元素?为什么?
- 被final修饰的变量,一定是编译期常量吗?
- final修饰的实例方法,子类中能不能定义同名同参数的方法?
2. static 关键字
【基础必考题(初中级100%覆盖)】
- 简述static关键字的核心作用,分别说明修饰变量、方法、代码块、内部类的语义
- 静态变量和实例变量的核心区别?存储位置有什么不同?
- 静态方法能不能直接访问实例成员(变量/方法)?为什么?
- 静态代码块、构造代码块、构造方法的执行顺序是什么?父子类场景下的完整执行顺序?
- 静态内部类和非静态内部类的核心区别?
【进阶深挖题(中高级高频)】
- static成员的生命周期?类加载的哪个阶段会初始化静态变量和静态代码块?
- 静态方法能不能被重写?为什么?(结合多态动态绑定机制说明)
- 静态变量存在线程安全问题吗?什么场景下会触发?
- static和final组合使用的核心场景?
public static final定义常量的规范与注意事项? - 类加载过程中,静态变量的初始化和静态代码块的执行顺序由什么决定?
【高频坑点题】
- 能不能在构造方法中给静态变量赋值?
- static代码块能不能捕获异常?
- 静态方法用synchronized修饰时,锁的对象是什么?和实例同步方法有什么区别?
3. volatile 关键字
【基础必考题(并发面试100%覆盖)】
- volatile关键字的两个核心作用是什么?
- 什么是指令重排序?volatile如何禁止指令重排序?
- 什么是内存可见性?volatile如何保证变量的可见性?
- volatile能不能保证原子性?为什么?举一个典型反例
- volatile和synchronized的核心区别?
【进阶深挖题(中高级/大厂必问)】
- 双重检查锁定(DCL)实现单例模式,为什么必须加volatile?
- volatile的内存屏障机制是什么?读/写操作前后分别插入什么类型的内存屏障?
- volatile对应的happens-before规则是什么?
- MESI缓存一致性协议和volatile的关系?
- volatile在JDK源码中的经典应用?(AtomicInteger、ConcurrentHashMap、ThreadPoolExecutor等)
- 什么是伪共享?volatile和伪共享的关系?如何解决伪共享问题?
【高频坑点题】
- 加了volatile的
count++操作,是线程安全的吗? - volatile修饰long/double类型变量,在32位JVM上读写是原子的吗?
- volatile和final能不能一起使用?
4. synchronized 关键字
【基础必考题(并发面试100%覆盖)】
- synchronized的三种使用方式?分别锁定的是什么对象?
- synchronized如何保证原子性、可见性、有序性?
- 什么是锁的可重入性?synchronized是可重入锁吗?底层如何实现?
- 一个线程调用对象的synchronized实例方法时,另一个线程能否调用同一个对象的非synchronized方法?
- synchronized和Lock接口的核心区别?
【进阶深挖题(中高级/大厂必问)】
- 详细说明synchronized的锁升级全流程(偏向锁→轻量级锁→重量级锁),包括每种锁的适用场景、升级触发条件、优缺点
- Java对象头的结构是什么?Mark Word在不同锁状态下分别存储什么内容?
- synchronized的底层实现原理?
monitorenter/monitorexit指令、Monitor监视器机制 - JDK6对synchronized做了哪些性能优化?(自适应自旋、锁消除、锁粗化、偏向锁、轻量级锁)
- 重量级锁的底层实现?为什么说重量级锁的性能开销大?
- synchronized的锁释放时机有哪些?方法抛出异常时会不会释放锁?
【高频坑点题】
- 两个线程分别调用同一个类的synchronized静态方法和synchronized实例方法,会不会发生互斥?为什么?
- 父类synchronized修饰的方法,子类重写后默认还是同步方法吗?
- 自旋锁一定比重量级锁效率高吗?
- 能不能在try块中加synchronized,在finally里手动释放锁?
5. transient 关键字
【基础必考题(初中级高频)】
- transient关键字的核心作用是什么?
- 哪些场景下需要使用transient关键字?
- 被transient修饰的变量,反序列化后的默认值是什么?
【进阶深挖题(中高频)】
- 被transient修饰的变量,真的完全不能被序列化吗?有没有例外情况?
- ArrayList的
elementData数组为什么要被transient修饰? - static变量会被序列化吗?加了transient的static变量呢?
- 自定义序列化的方式有哪些?如何手动控制transient变量的序列化与反序列化?
Externalizable和Serializable接口的区别?对transient关键字的处理有什么不同?
【高频坑点题】
- transient修饰的final变量,能不能被序列化?
- 静态内部类的transient变量,序列化有什么限制?
二、跨关键字 综合对比高频题(面试必问拉分题)
- 从原子性、可见性、有序性、性能、使用场景等维度,对比volatile和synchronized的核心区别
- final和static的核心区别?二者组合使用的场景与注意事项
- synchronized和volatile的有序性保证,底层实现有什么区别?
- transient和static的核心区别?为什么static变量天然不参与序列化?
- final、volatile、synchronized在保证多线程可见性上的区别与实现原理?
- 同时使用static和synchronized,和普通实例synchronized方法有什么本质区别?
三、场景手撕与实战题(面试高频手写+原理讲解)
- 手写双重检查锁定(DCL)单例模式,解释每一步的作用,尤其是volatile的核心作用
- 基于final关键字,手写一个线程安全的不可变类
- 写出父子类场景下,静态代码块、构造代码块、构造方法的完整执行顺序代码,并解释执行逻辑
- 手写自定义序列化示例,手动控制transient变量的序列化与反序列化
- 写出volatile的正确使用场景示例、错误使用场景示例,并解释背后的原因
四、大厂加分深挖题(资深开发/高频)
- 详细讲解volatile的内存屏障在x86架构下的具体实现
- synchronized的锁撤销、锁降级机制的适用场景与实现原理
- JSR-133内存模型中,对final字段的重排序规则做了哪些规范?
- 为什么说synchronized是不公平锁?有没有办法实现公平的synchronized?
- JDK8前后,静态变量的存储位置发生了什么变化?对static关键字的使用有什么影响?
- 序列化机制中,transient关键字和
serialVersionUID有什么关联?