当前位置: 首页 > backend >正文

对于final、finally和finalize不一样的理解

目录

1、final

1.1、不可变性(Immutability)

1.2、内存可见性(Visibility)

1.3、初始化安全(Initialization Safety)

1.4、禁止重排序(Reordering)

1、静态常量

2、实例常量

1.5、与不可变对象的结合

1.6、final 对象引用的内部状态

1.7、适用场景

2、finally

2.1、介绍

1. 资源释放

2. 日志记录或清理操作

2.2、执行顺序

2. 避免在 finally 中使用 return

2.3、注意事项

1. 不会执行的情况

3、finalize

3.1、定义

1. 作用

2. 调用时机

3.2、问题与缺陷

3.3、解决方案


关于finally和finalize的介绍,可参考:对Java 资源管理和引用体系的介绍-CSDN博客


1、final

        在 Java 中,使用 final 关键字修饰成员变量,尤其是不可变对象可以 保证线程安全,其核心原因在于 final 通过以下机制确保了变量的可见性和初始化顺序。

1.1、不可变性(Immutability)

        final 变量一旦初始化后不可修改,如果一个成员变量被声明为 final,则它 只能在构造函数中赋值一次,之后无法被修改。

示例

public class ImmutableClass {private final int value;public ImmutableClass(int value) {this.value = value; // 初始化}
}
  • 线程安全的关键
    多个线程读取该变量时,无需担心其他线程修改其值,因此 无需加锁 或其他同步机制。

1.2、内存可见性(Visibility)

当一个类被构造时,如果某个字段被声明为 final,JVM 会确保:

  • 所有对 final 字段的写入必须在构造函数结束前完成。
  • 构造函数结束时,JVM 会插入一个 写屏障(Write Barrier),确保这些 final 字段的值被刷新到主内存中。

1、final变量

        Java 内存模型(JMM)规定,对 final 变量的写入操作会自动刷新到主内存,通过内存屏障(Memory Barrier)强制刷新缓存确保其他线程读取时能获取最新值。

2、非 final 变量

        非 final 变量可能因为线程缓存(如 CPU 缓存)导致多个线程看到不一致的值。

JVM 在处理 final 字段时会自动插入以下类型的内存屏障:

1.3、初始化安全(Initialization Safety)

         对 final 变量的写入会在对象引用赋值给其他线程之前完成。

        当一个对象被构造完成后,其他线程访问该对象的 final 成员变量时,可以确保它们已经初始化完成,且不会看到“半初始化”的状态。

示例:

public class SafeInit {private final int data;public SafeInit(int data) {this.data = data; // 构造函数中初始化 final 变量}
}// 线程 A 创建对象
SafeInit obj = new SafeInit(42); 
// 线程 B 访问 obj.data
System.out.println(obj.data); // 保证看到 42
  • 对比非 final 变量

        如果变量未被声明为 final,构造函数中的写入可能被重排序(Reordering),导致其他线程看到未初始化的值。

1.4、禁止重排序(Reordering)

final 变量的写入具有 happens-before 语义

        构造函数中对 final 变量的写入操作 在对象引用对外可见之前完成。其他线程访问该对象的 final 变量时,会看到构造函数中设置的值,而不是默认值(如 0 或 null)。

1、静态常量

static final 变量特点

        可以在声明时直接赋值,也可以通过 static 代码块赋值。在类加载时初始化一次。属于类级别,不是某个实例的属性。

public class Example {// 声明时直接赋值private static final int A = 1;// 通过 static 代码块赋值private static final int B;static {B = 2;}
}
  • 结论static final 变量不需要构造函数初始化,因为它们与类的实例无关。

2、实例常量

普通 final 变量特点:属于实例级别,每个对象都有自己的值。

必须在以下位置之一初始化:

1、声明时直接赋值。

2、在构造函数中赋值。

3、在实例初始化块中赋值。

public class Example {// 声明时直接赋值private final int a = 1;// 实例变量private final int b;// 构造函数赋值public Example() {b = 2;}// 实例初始化块赋值private final int c;{c = 3;}
}

结论

        普通 final 变量必须确保在对象创建时完成初始化,因此必须通过构造函数、声明或实例初始化块实现。

    1.5、与不可变对象的结合

            如果 final 变量引用的是一个不可变对象(如 StringInteger),则整个对象的状态在构造完成后不会改变,进一步增强了线程安全性。

    示例:

    public class Config {private final String host;private final int port;public Config(String host, int port) {this.host = host;this.port = port;}
    }
    
    • 线程安全的原因
      所有线程共享的 Config 对象的状态是固定的,无需担心并发修改。

    1.6、final 对象引用的内部状态

             final 只保证引用不可变,不保证对象内部状态不可变。

            如果 final 变量引用的是一个可变对象(如 ListMap),虽然引用不可变,但对象内部的状态仍可能被修改。

    示例:

    public class MutableRef {private final List<String> list = new ArrayList<>();public void add(String item) {list.add(item); // 允许修改内部状态}
    }
    

            多个线程调用 add() 方法时,会出现线程安全问题,此时仍需通过同步机制(如 synchronized 或 ConcurrentHashMap)保证线程安全。

    1.7、适用场景

    • 常量配置:如数据库连接字符串、系统参数等。
    • 不可变对象:如 StringLocalDateTime 等。
    • 线程安全工具类:如缓存键、状态标识符等。

    注意事项

    • final 不能替代同步:如果变量引用的是可变对象,仍需通过同步机制保护内部状态。
    • final 与 volatile 的区别
      • final 保证初始化安全和不可变性。
      • volatile 保证变量的可见性和有序性,但允许修改。

            通过合理使用 final,可以显著简化多线程代码的设计,减少竞态条件和内存一致性错误的风险。


    2、finally

    了解更详细的finally的使用,可参考:合理管控Java语言的异常-CSDN博客

    2.1、介绍

        finally 是异常处理机制的一部分,用于定义 无论是否发生异常都需要执行的代码块。它通常与 try 和 catch 配合使用,确保资源释放或关键操作在程序流程中始终执行。

    try {// 可能抛出异常的代码
    } catch (ExceptionType e) {// 异常处理逻辑
    } finally {// 无论是否发生异常,都会执行的代码
    }
    

    1. 资源释放

    finally 常用于释放文件、网络连接、数据库连接等资源,确保资源不被泄漏。

    示例:关闭文件流

    FileInputStream fis = null;
    try {fis = new FileInputStream("file.txt");// 读取文件...
    } catch (IOException e) {e.printStackTrace();
    } finally {if (fis != null) {try {fis.close(); // 确保关闭文件流} catch (IOException e) {e.printStackTrace();}}
    }
    

            ⚠️ 从 Java 7 开始,推荐使用 try-with-resources 语法自动管理资源,无需显式写 finally:

    try (FileInputStream fis = new FileInputStream("file.txt")) {// 自动关闭 fis
    } catch (IOException e) {e.printStackTrace();
    }
    

    2. 日志记录或清理操作

    例如,记录操作日志、清理临时文件等。

    2.2、执行顺序

    1. 无论是否抛出异常,finally 块都会执行

            即使 try 或 catch 块中有 return、 break或 continue,finally 仍会执行(除非程序提前终止)。

    2. finally 的执行时机

     或 catch 块执行完毕后,finally 会立即执行。

    如果 try 或 catch 中有 return,finally 会在 return 之前执行。

    示例:

    public class FinallyReturnExample {public static void main(String[] args) {System.out.println(test());}public static int test() {try {System.out.println("try 块");return 1;} catch (Exception e) {System.out.println("catch 块");return 2;} finally {System.out.println("finally 块");}}
    }输出:
    try 块
    finally 块
    1注意:finally 中的代码会在 return 之前执行,但 不会改变返回值。如果 finally 中也有 return,则会覆盖之前的返回值(需谨慎使用)。

    2. 避免在 finally 中使用 return

    如果 finally 中有 return,会覆盖 try 或 catch 中的返回值,导致逻辑混乱。

    public static int test() {try {return 1;} finally {return 2; // 覆盖前面的 return}
    }
    

    2.3、注意事项

    1. 不会执行的情况

            如果 try 或 catch 中调用了 System.exit(0),程序会终止,finally 不会执行。

            如果程序因 JVM 异常崩溃(如 OutOfMemoryError),finally 也不会执行。

            如果 try 或 catch 中存在 无限循环(如 while (true) {}),程序会卡死在循环中,finally 不会执行。

    总结:

            注意:finally 中的代码会在 return 之前执行,但 不会改变返回值。如果 finally 中也有 return,则会覆盖之前的返回值(需谨慎使用)。

            如果在try和catch里面分别有return,则会先打印输出的时候,finally在try和catch的return之前打印。

    3、finalize

    ⚠️ finalize() 在 Java 9 后已被弃用(deprecated),不建议依赖其进行资源清理。  

    3.1、定义

            finalize() 是 Object 类中的一个方法,用于在对象被 垃圾回收(GC) 之前执行一些清理操作(如释放资源)。从 Java 9 开始,finalize() 被标记为 @Deprecated(废弃),不再推荐使用。

            通常记录对象被回收的时机或状态。

    public class Resource {@Overrideprotected void finalize() throws Throwable {try {// 释放资源System.out.println("资源已释放");} finally {super.finalize();}}
    }
    

    1. 作用

    • 在对象被 GC 回收之前,JVM 会自动调用该对象的 finalize() 方法。
    • 通常用于释放非内存资源(如文件句柄、网络连接等)。

    2. 调用时机

    • 对象不再被任何强引用指向时(即变为不可达对象),GC 会将其标记为可回收。
    • GC 在回收对象前,可能调用其 finalize() 方法(具体时机由 JVM 决定,不确定)。

    3.2、问题与缺陷

    1. 不确定性

    GC 时间不可控

            JVM 何时触发 GC 是不确定的,finalize() 的执行时机也无法预测。可能永远不会执行

    2. 性能问题

    GC 负担加重

            每个需要调用 finalize() 的对象会被放入 finalize 队列,由低优先级的守护线程处理,导致 GC 效率降低。可能导致资源延迟释放,甚至引发内存泄漏。

    3. 不可靠性

    异常未处理

            finalize() 中抛出异常可能导致整个 GC 过程失败,且没有恢复机制。   多个对象的 finalize() 执行顺序无法保证。

    3.3、解决方案

    关于cleaner和PhantomReference可参考:对Java 资源管理和引用体系的介绍-CSDN博客

    1.1、使用try-with-resources

    try (FileInputStream fis = new FileInputStream("file.txt")) {// 使用资源
    } catch (IOException e) {e.printStackTrace();
    }
    

    2、Cleaner接口

    java9开始。

    import java.lang.ref.Cleaner;public class Resource {private static final Cleaner CLEANER = Cleaner.create();private final Cleaner.Cleanable cleanable;public Resource() {cleanable = CLEANER.register(this, () -> {// 清理逻辑System.out.println("资源已清理");});}
    }
    

    3、PhantomReference + ReferenceQueue

    • 通过虚引用配合引用队列,实现资源回收的回调。
    import java.lang.ref.PhantomReference;
    import java.lang.ref.ReferenceQueue;public class PhantomReferenceExample {public static void main(String[] args) {ReferenceQueue<Resource> queue = new ReferenceQueue<>();PhantomReference<Resource> ref = new PhantomReference<>(new Resource(), queue);// 模拟 GCSystem.gc();// 检查队列中是否有被回收的对象if (ref.isEnqueued()) {System.out.println("对象已被回收");}}
    }
    

    总结

    http://www.xdnf.cn/news/8016.html

    相关文章:

  • Java基于SSM的数学辅导微信小程序【附源码、文档说明】
  • 招投标项目记录
  • 一键二次元风格转换:风格转换 ComfyUI 使用教学--
  • 逆向学习笔记1
  • 【性能提升300%】Function Calling高并发实践:gRPC优化+缓存策略+容错设计​
  • 2024正式版企业级在线客服系统源码+语音定位+快捷回复+图片视频传输+安装教程
  • id分页遍历数据漏行问题
  • 猎板PCB如何以高可靠方案护航大国重器?
  • 发布Chrome浏览器插件的几种方法
  • C++进阶--C++11
  • C++ stack对象创建、入栈、获取栈顶
  • MySQL高可用实战:PXC集群原理与部署全解析,让数据库永不宕机
  • vue页面实现table动态拆分列功能
  • 江科大TIM定时器hal库实现
  • 自定义属性面板开发指南:公开属性声明、监听回调与基础类型配置
  • Linux:缓冲区
  • BigFoot (DBM) Deadly Boss Mods
  • DL00988-稀疏增强数据transformer船舶AIS轨迹预测含完整数据集
  • 腾讯文档怎么设置多列筛选条件
  • 固定翼无人机抛投技术分析!
  • 从零基础到最佳实践:Vue.js 系列(5/10):《状态管理》
  • 11-帮助中心
  • cmd如何从C盘默认路径切换到D盘某指定目录
  • 前端之vue3创建基本工程,基本登录、注册等功能的完整过程
  • 【IC验证】systemverilog_包
  • 自由开发者计划 001:创建一个用于查看 Jupyter Notebook 的谷歌浏览器插件 Jupyter Peek
  • 常见的LLM
  • 从零基础到最佳实践:Vue.js 系列(2/10):《模板语法与数据绑定》
  • 对抗学习(AL),生成对抗网络(GAN),强化学习,RLHF
  • 【差异分析】t-test