一文掌握 Object 类里的所有方法(wait、notify、finalize)

Object 概述

Object 类是 Java 中所有类的父类,这个类中包含了若干方法,这也就意味着所有类都将继承这些方法。因此,掌握这个类的方法是非常必要的,毕竟所有类都能为你提供这些方法。

Object 类位于 java.base 模块下 java.lang.Object,其结构如下:
Object 中的那些方法
其中 wait0 是私有方法,不用管。我把这些方法分为两类,一类是常用方法(如 hashCodeequals );一类是线程相关方法(如 waitnotify)。
下面就对这些方法一一说明,进而全面掌握这个类的所有方法。

常用方法

1. getClass() 方法

public final native Class<?> getClass();

返回这个类对应的 Class 对象。final 方法,子类无法覆写。Class 对象是 Java 反射中最重要的一个类。有关反射的内容可以查看这个文章:Java Reflection 反射使用 完全指南

2. hashCode() 方法

public native int hashCode();

返回这个对象的哈希值。默认情况下,Object 是返回对象在堆内存中的地址。一般来说,如果重写了 equals 方法时,一般也会重写这个方法。
另外,当时你使用 HashMap 这种需要对象哈希值的集合的时候,Java 会自动调用这个方法,用以确定这个对象对应的值放到哪个位置。

3. equals(Object) 方法

public boolean equals(Object obj) {return (this == obj);}

这个方法用于判断当前对象是否与传入的 obj 对象相等。在 Object 中,就是使用 == 来进行判断,即判断两个对象在内存中的地址是否相同。但是一般情况下,子类常常需要覆写此方法,来对不同的类做不同的相等判断。

重点注意,如果覆写了 equals 方法的话,也需要将 hashCode 覆写了。这一点也好理解,如果两个对象是相等的,那么这两者的所有内容包括哈希值也应该相同才对。在集合中(如 List),往往会调用 equals 方法,来判断存入的对象是否相同。

在写 equals 时,往往可以参考 Java 中其他类的 equals 方法。这里先给出一个取自于 android.health.connect.datatypes.units.Lengthequals 方法,大家在写的时候可以参照:

@Override
public boolean equals(Object object) {if (this == object) return true;if (object instanceof Length) {Length other = (Length) object;return this.getInMeters() == other.getInMeters();}return false;
}

4. clone() 方法

protected native Object clone() throws CloneNotSupportedException;

首先要注意到,这个方法是 protected 的。对于 Object 来说,这个方法返回当前对象的一个浅拷贝,而且只有实现了 Cloneable 接口才可以调用该方法,否则抛出 CloneNotSupportedException 异常。

另外提一点,通过调用 clone 方法创建的对象,是不会调用其构造方法的。
其实这个方法是比较鸡肋的方法,Cloneable 这个注解也并不是一个好的设计。应该避免使用。

5. toString() 方法

public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());}

这个方法太常用了,一般子类都会覆写。用于返回对象信息。

线程相关方法

1. wait() 方法

public final void wait() throws InterruptedException {wait(0L);}

其实线程相关的这几个方法都是关联的,懂了其中两个方法就懂了其他的。关键还是在于对锁的理解。

大家基本都知道这个方法就是让线程等待,但怎么等待,又怎么唤醒,估计大部分人很难说明其用法。

首先,这个 wait 方法确实是让线程等待,但其与 sleep 不同,如果你直接在线程中的调用,会出现java.lang.IllegalMonitorStateException 异常,如下:

public class Hello {public static void main(String[] args) {Object obj = new Object();System.out.println("before wait---");try {obj.wait();} catch (InterruptedException exception) {exception.printStackTrace();}System.out.println("after wait---");}
}

异常信息:

mi@mi-HP:~/develop/code/JavaCode$ java Hello.java 
before wait---
Exception in thread "main" java.lang.IllegalMonitorStateException: current thread is not ownerat java.base/java.lang.Object.wait0(Native Method)at java.base/java.lang.Object.wait(Object.java:375)at java.base/java.lang.Object.wait(Object.java:348)at Hello.main(Hello.java:24)

可以看到,走到 obj.wait() 时发生了崩溃,IllegalMonitorStateException 是一个运行时异常,翻译过来就是“非法监视器状态异常”。它表示线程在没有持有相应监视器锁的情况下执行 waitnotify 等操作,而后面的描述 “current thread is not owner”,也表示当前线程并不是持有者。那么当前线程不是谁的持有者呢?

Java 规定,只有已经获取锁的线程,才可以调用锁的 wait()notify()方法,这个锁是同步代码块,也可以是同步方法。上面说的线程不是持有者,其实就是这个锁的持有者。下面我们更改一下代码:

    public static void main(String[] args) {Object obj = new Object();synchronized(obj) {            //同步代码块,持有锁 objSystem.out.println("before wait---");try {obj.wait();} catch (InterruptedException exception) {exception.printStackTrace();}System.out.println("after wait---");}}

再运行一下,程序正常运行,没有抛出 IllegalMonitorStateException 异常,并在打印 “before wait—” 后等待在那里,线程进入阻塞状态:

mi@mi-HP:~/develop/code/JavaCode$ java Hello.java 
before wait---

此处注意,你在 synchronized 中添加的锁对象,必须与你调用 wait 方法的对象一致,否则仍然会出现 IllegalMonitorStateException 异常。简单来说就是你在哪个对象上调用 wait,就应该将这个对象作为锁持有。

那么有人就问了,为啥这么设计,这么设计有什么用,适用于什么场景?

之所以 wait 方法需要在同步方法或是同步代码块中调用(synchronized),是因为 wait 就是释放当前的锁,既然要释放,那么就意味着必须得先得到这个锁。而调用 notifynotifyAll 是将锁交给含有 wait 方法的线程,让其继续执行下去。如果自身没有锁,那么唤醒其他 wait 的线程让其参与锁的竞争就无从谈起了。

在 Java 平台中,每个对象都有一个唯一与之对应的内部锁(Monitor),此外,Java 虚拟机会为每个对象维护两个集合:一个 EntrySet(入口集),一个 WaitSet(等待集)。对于任意对象 obj,其 EntrySet 用于存储等待获取 obj 对应的内部锁的所有线程,WaitSet 用于存储执行了 obj.waitobj.wait(long) 的线程。

对于对象的非同步方法,任意时刻,可以有任意个线程调用该方法。

对于对象的同步方法,只有拥有这个对象的锁,才能调用这个同步方法。如果这个锁被其他线程占用,那么另外一个调用该同步方法的线程就会处于阻塞状态,并进入这个对象的 EntrySet
若一个已经拥有独占锁的线程调用了该对象 wait 方法,那么该线程会释放独占锁,并加入到 WaitSet

那么为什么线程都持有了这个锁了,明明可以执行相关任务,为什么会调用 wait 释放锁呢,这个线程的后续任务怎么执行呢?对于这种问题,我们可以这样想象这种场景,你占用了一个房间,准备把老师布置的作业写完,可你正写到一半,另一位同学突然进来,说要占用这个房间开个会。此时你就需要释放这个房间,然后等待这个同学开完会再把房间给你,你继续使用。

你占用房间那就是 synchronized(房间),你暂时释放房间就是 房间.wait,此时你进入到 房间的 WaitSet;别人用完了房间通知你,就是 房间.notify,然后你进入到 房间的EntrySet,等竞争到 房间 后继续写作业。

而某个线程调用 notify()notifyAll() 方法,就是将 WaitSet 中的线程转移到 EntrySet,然后让他们竞争锁。

由此可见,无论你是 wait 还是 notify,都是对这个对象的锁的操作,因此你必须先持有这个对象锁,否则就是 IllegalMonitorStateException 异常。

下面来看一个简单的代码:

public static void main(String[] args) {Object obj = new Object();Thread thread_1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized(obj) {try{System.out.println("threa_1 before wait... "+Thread.currentThread().getState());obj.wait();System.out.println("threa_1 after wait... "+Thread.currentThread().getState());} catch (IllegalMonitorStateException|InterruptedException exception) {exception.printStackTrace();}}}}, "thread_1");Thread thread_2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized(obj) {try{System.out.println("thread_2 sleep 2 seconds...");Thread.sleep(2000);System.out.println("thread_2 notify...");obj.notify();} catch (IllegalMonitorStateException|InterruptedException exception) {exception.printStackTrace();}}}}, "thread_2");thread_1.start();thread_2.start();}

thread_1 执行时调用 wait 方法,两秒后被 thread_2 调用 notify 唤醒,唤醒后 thread_1 继续执行。
输出结果:

mi@mi-HP:~/develop/code/JavaCode$ java Hello.java 
threa_1 before wait... RUNNABLE
thread_2 sleep 2 seconds...
thread_2 notify...
threa_1 after wait... RUNNABLE

这里还有需要注意的一点就是,虽然 thread_1 被唤醒,但是 thread_1 线程并不是能立即执行的。被唤醒只是说明 thread_1objWaitSet 进入到了 EntrySet,此时的线程状态是 BLOCKED,还需要竞争 obj 锁。当得到 obj 锁之后,才能够继续执行。
诸位可以在 thread_2notify 之后加上 sleep 两秒看看效果。

好了,为了讲解 wait 方法,这里扯了一大堆关于线程等待与唤醒的内容,也只有理解了这些内容,才能明白 wait 方法的作用。

那么这里总结一下,wait 方法用于同步代码块中,用于让当前线程等待,进入对象的 WaitSet。其他线程需要调用对象的 notify 方法,使其被唤醒,进入 EntrySet,再竞争对象锁,获取锁之后将继续执行。

2. wait(long) 方法

public final void wait(long timeoutMillis) throws InterruptedException {long comp = Blocker.begin();try {wait0(timeoutMillis);} catch (InterruptedException e) {Thread thread = Thread.currentThread();if (thread.isVirtual())thread.getAndClearInterrupt();throw e;} finally {Blocker.end(comp);}
}

wait 方法是无限期等待,必须其他线程调用 notify,而这个带参数的,就是限定了等待时间,超过了这个时间,线程会自己唤醒自己。

另外,通过 wait 的代码可以看到,当参数为 0 时,这个方法其实就是 wait 的无限期等待。而这个方法中,真正让线程进入等待的是 wait0 这个 native 方法:

private final native void wait0(long timeoutMillis) throws InterruptedException;

3. wait(long, int) 方法

public final void wait(long timeoutMillis, int nanos) throws InterruptedException {if (timeoutMillis < 0) {throw new IllegalArgumentException("timeoutMillis value is negative");}if (nanos < 0 || nanos > 999999) {throw new IllegalArgumentException("nanosecond timeout value out of range");}if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {timeoutMillis++;}wait(timeoutMillis);
}

该方法与 wait(long timeout) 方法类似,只是多了一个 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。

如果 timeoutnanos 参数都为 0,则不会超时,会一直进行等待,等同于 wait() 方法。

4. notify 方法

public final native void notify();

这个方法前面说过,是用于唤醒 WaitSet 中的线程,使其进入到 EntrySet 中。但是往后会发现还有一个 notifyAll 的方法,那么这两个方法有什么区别呢?

当你调用 notify 时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。虽然如果你调用 notifyAll 方法,那么等待该锁的所有线程都会被唤醒,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁定。简单来说,notify 只会唤醒一个线程,notifyAll 将唤醒所有线程。

5. notifyAll 方法

public final native void notifyAll();

唤醒所有等待中的线程。

在线程中,生产者和消费者模型是我们常常用以演示线程同步的,下面是一个典型的生产者消费者例子,看懂了这个例子,waitnotify 基本就没什么问题了:

public class Main {private static final Queue<Integer> queue = new LinkedList<>();private static final int MAX_SIZE = 5;private static final Object lock = new Object();public static void main(String[] args) {Thread producer = new Thread(() -> {while (true) {synchronized (lock) {while (queue.size() == MAX_SIZE) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}queue.add(1);lock.notifyAll();}}});Thread consumer = new Thread(() -> {while (true) {synchronized (lock) {while (queue.isEmpty()) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}queue.poll();lock.notifyAll();}}});producer.start();consumer.start();}
}

finalize() 方法

protected void finalize() throws Throwable { }

最后,我们来说一下 finalize 方法,这个方法虽然被标记废弃,但是之前还是比较常用的。它在对象被 GC 回收之前调用,一般覆写这个方法完成这个对象的清理工作,例如清理相关的 native 资源或是其他资源(socket、文件)的释放。

当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了 finalize 方法,若未覆盖,则直接将其回收。否则,若对象未执行过 finalize 方法,将其移动到一个队列里,由一个低优先级线程执行该队列中对象的 finalize 方法。执行 finalize 方法完毕后,这些对象才成为真正的垃圾,等待下一轮垃圾回收。

以下是一个 finalize 使用例子:

class FinalizeObj {private long nativePointer;public FinalizeObj() {nativePointer = createNative();}@Overrideprotected void finalize() throws Throwable {super.finalize();releaseNative(nativePointer);nativePointer = 0L;}private native long createNative();private native void releaseNative(long nativePointer);
}

这个例子,在构造方法中创建 native 底层资源,在 finalize 方法中释放 native 底层资源。

不过,现在由于 finalize 被标记为废弃,已经不推荐这么写了。至于为什么会被标记为废弃,主要是因为其被执行的不确定性太大,一个对象从不可达到 finalize 方法被执行,完全依赖 JVM。这无法保证此对象被占用的资源被及时回收,甚至都不能保证这个方法被执行。因此要避免使用。
其实如果这个方法真的好用的话,也不会有那么多的类要提供 closedestroy 等方法了。

那么既然这个方法不推荐,那我要释放上面那个例子中的 native 资源,应该怎么做呢?答案是使用 java.lang.ref.Cleaner,这是 Java 9 推出的一个轻量级垃圾回收机制。不过这个类加到文章里来就太长了。

总结

通过这篇文章,大家应该对 Object 里面的那些方法有一些了解,常用的5个方法较为简单。主要是与线程相关的方法,这才是 Object 类的重头戏。好在只要掌握的 waitnotify 方法,其他的就明白了。最后文章讲解了一下 finalize 方法,作为一个被废弃的方法,我们了解了它的使用方法,后续需要用 Cleaner 等方法替代。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/363887.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

360的chromesafe64.dll动态链接库导致chrome和edge浏览器闪退崩溃关闭

在chrome或edge浏览器中打开特定的一些网页会导致浏览器闪退关闭 这是windows系统记录的报错日志 chrome报错日志 edge报错日志 日志指向的就是chromesafe64.dll这个动态库 然后用everything搜索发现原来在360安装目录下 360安装目录下的chromesafe64.dll文件 为什么360中的…

微信小程序服务器从腾讯云迁移到阿里云出现的坑

微信小程序服务器从腾讯云迁移到阿里云出现的坑 背景 原先小程序后台服务器到期&#xff0c;因为之前买的是腾讯云新用户&#xff0c;便宜&#xff0c;到期后续费金额懂的都懂。就在阿里云用新用户买了个新的&#xff0c;遂把服务全转到了阿里云服务器上。 此时&#xff0c;域…

Python学习笔记26:进阶篇(十五)常见标准库使用之性能测试cProfile模块学习使用

前言 本文是根据python官方教程中标准库模块的介绍&#xff0c;自己查询资料并整理&#xff0c;编写代码示例做出的学习笔记。 根据模块知识&#xff0c;一次讲解单个或者多个模块的内容。 教程链接&#xff1a;https://docs.python.org/zh-cn/3/tutorial/index.html 本文主要…

如何保护磁盘数据?电脑磁盘数据怎么保护?

电脑磁盘是存储数据的基础&#xff0c;可以将各种重要数据保存在其中。为了避免数据泄露&#xff0c;我们需要保护磁盘数据。那么&#xff0c;电脑磁盘数据怎么保护呢&#xff1f;下面我们就一起来了解一下吧。 文件夹加密超级大师 文件夹加密超级大师是一款优秀的电脑数据加密…

敏捷开发笔记(第9章节)--开放-封闭原则(OCP)

目录 1&#xff1a;PDF上传链接 9.1 开放-封闭原则&#xff08;OCP&#xff09; 9.2 描述 9.3 关键是抽象 9.3.1 shape应用程序 9.3.2 违反OCP 糟糕的设计 9.3.3 遵循OCP 9.3.4 是的&#xff0c;我说谎了 9.3.5 预测变化和“贴切的”结构 9.3.6 放置吊钩 1.只受一次…

2024年华东杯B题数学建模论文:基于车辆运动学转弯模型的自动驾驶规划问题

摘要 随着自动驾驶技术的发展&#xff0c;车辆转弯问题成为关键挑战。本文针对自动驾驶车辆在转弯过程中的数学建模、路径规划及避障策略进行了深入研究&#xff0c;旨在提升自动驾驶车辆的行驶安全性与效率。 针对问题1&#xff0c;对于四轮前轮驱动车辆的转弯问题&#xff0c…

MCU解决800V电动汽车牵引逆变器的常见设计挑战的3种方式

电动汽车 (EV) 牵引逆变器是电动汽车的。它将高压电池的直流电转换为多相&#xff08;通常为三相&#xff09;交流电以驱动牵引电机&#xff0c;并控制制动产生的能量再生。电动汽车电子产品正在从 400V 转向 800V 架构&#xff0c;这有望实现&#xff1a; 快速充电 – 在相同…

嵌入式Linux系统编程 — 4.5 strcmp、strchr 等函数实现字符串比较与查找

目录 1 字符串比较 1.1 strcmp() 函数 1.2 strncmp() 函数 1.3 示例程序 2 字符串查找 2.1 strchr() 函数 2.2 strrchr() 函数 2.3 strstr() 函数 2.4 strpbrk() 函数 2.5 示例程序 1 字符串比较 strcmp() 和 strncmp() 函数是C语言标准库中用于比较两个字符串的函…

【STM32修改串口波特率】

STM32微控制器中的串口波特率调整通常涉及到USART&#xff08;通用同步接收器/发送器&#xff09;模块的配置。USART模块提供了多个寄存器来设置波特率&#xff0c;其中关键的寄存器包括BRR&#xff08;波特率寄存器&#xff09;和USART_CR1&#xff08;控制寄存器1&#xff09…

JVM专题十:JVM中的垃圾回收机制

在JVM专题九&#xff1a;JVM分代知识点梳理中&#xff0c;我们主要介绍了JVM为什么采用分代算法&#xff0c;以及相关的概念&#xff0c;本篇我们将详细拆分各个算法。 垃圾回收的概念 垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;确实是计算机编程中的…

python-斐波那契数列

[题目描述] 斐波那契数列是指这样的数列&#xff1a;数列的第一个和第二个数都为 1&#xff0c;接下来每个数都等于前面 2个数之和。 给出一个正整数 a&#xff0c;要求斐波那契数列中第 a 个数是多少。输入&#xff1a; 第 1 行是测试数据的组数 n&#xff0c;后面跟着 n 行输…

Dahlia Hart: Stylized Casual Character(休闲角色模型)

此包包含两个发型和两个服装&#xff0c;每个都有多种颜色选择。每个发型都适合与物理资源一起使用&#xff0c;并包含各种表情和音素混合形状。 下载&#xff1a;​​Unity资源商店链接资源下载链接 效果图&#xff1a;

算法设计与分析--近似算法内容整理

文章目录 P、NP、NP-hard 和 NPC多项式时间概念区分NP-hard 的证明例题 1 证明 T S P TSP TSP 问题是 N P − h a r d NP-hard NP−hard 问题 。例题 2 证明最大加权独立集问题是 N P − h a r d NP-hard NP−hard 问题。 扩展 NP-hard 问题3-SAT 问题TSP 旅行商问题 Load B…

探索高效开发神器:Blackbox AI(免费编程助手)

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 &#x1f916; 想要代码生成&#xff1f;&#x1f44c; &#x1f4ac; 需要和AI聊天解决难题&#xff1f;&#…

ORBSLAM3_ROS_Ubuntu18_04环境搭建安装

orbslam3安装 ORB-SLAM3配置及安装教程&#xff08;2023.3&#xff09;_orbslam3安装-CSDN博客 换源&#xff0c;换成国内的 搜索software 安装工具 sudo apt install git sudo apt update sudo apt install gcc g cmake安装 cmake安装新版本 ubuntu20.04安装cmake详细…

python--基础篇--正则表达式--py脚本--题目解答

文章目录 验证输入用户名和QQ号是否有效并给出对应的提示信息从一段文字中提取出国内手机号码替换字符串中的不良内容拆分长字符串 验证输入用户名和QQ号是否有效并给出对应的提示信息 """ 验证输入用户名和QQ号是否有效并给出对应的提示信息要求&#xff1a;用…

Nvidia Jetson/RK3588+AI双目立体相机,适合各种割草机器人、扫地机器人、AGV等应用

双目立体视觉是基于视差原理&#xff0c;依据成像设备从不同位置获取的被测物体的图像&#xff0c;匹配对应点的位置偏移&#xff0c;得到视差数据&#xff0c;进而计算物体的空间三维信息。为您带来高图像质量的双目立体相机&#xff0c;具有高分辨率、低功耗、远距离等优点&a…

石家庄高校大学智能制造实验室数字孪生可视化系统平台项目验收

智能制造作为未来制造业的发展方向&#xff0c;已成为各国竞相发展的重点领域。石家庄高校大学智能制造实验室积极响应国家发展战略&#xff0c;结合自身优势&#xff0c;决定引进数字孪生技术&#xff0c;构建一个集教学、科研、生产于一体的可视化系统平台。 数字孪生可视化…

STM32小项目———感应垃圾桶

文章目录 前言一、超声波测距1.超声波简介2.超声波测距原理2.超声波测距步骤 二、舵机的控制三、硬件搭建及功能展示总结 前言 一个学习STM32的小白~ 有问题请评论区或私信指出 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、超声波测距 1.超声波…

rclone 上传资料到 onedrive 遇到限速问题解决

原因分析 可能和脚本参数设置有关系,我的参数是: rclone copy "F:\阿里云盘\6666\局域网" "od:影视" --ignore-existing -u -v -P --transfers20 --ignore-errors --buffer-size128M --check-first --checkers10 --drive-acknowledge-abuse差不多8G大小的…