记录下自己阅读过程的笔记,如有错误,欢迎指正!
1. 基本介绍
- 在 Java 中,
Object
类是类层次结构的根类 - 几乎每个 Java 类都直接或间接继承自
Object
类,意味着每个类都继承了Object
的方法 - 类结构:
2. 源码分析
2.1 静态代码块及本地方法注册
// 本地方法的声明(使用 native 关键字)
private static native void registerNatives();
static { // 一个静态代码块
registerNatives();
}
-
本地方法声明:
registerNatives()
- 关键字
native
表示这个方法是通过本地代码(如 C/C++)实现的接口,这些代码直接与底层系统的资源或系统 API 交互
registerNatives()
方法的主要作用是在内存中注册由本地方法实现的一些关键函数,这些函数通常是性能敏感的,直接与操作系统层交互。这样做可以提高方法调用的效率,避免每次调用时都进行查找和链接过程 - 关键字
-
静态初始化块:
- 在类被 Java 虚拟机加载时执行,且只执行一次,静态代码块常用于执行类级别的初始化代码
- 代码块中调用了
registerNatives()
方法来初始化与对象操作相关的本地方法,确保这些方法在被 Java 代码调用前已正确链接
2.2 getClass()
// 返回当前对象所属的类的类对象
public final native Class<?> getClass();
- 这里的
Class<?>
表示方法返回一个Class
类型的对象,其中?
是一个通配符,表示任何类型。在实际使用中,这个返回类型会更具体地表达为Class<? extends |X|>
,这里的|X|
表示调用getClass()
的对象的类型 - 功能描述:
getClass()
方法返回一个Class
类型的对象,该对象在 Java 中代表了调用此方法的对象的实际运行时类,通过这个返回的Class
对象,可以访问关于类的各种信息,如类的名字、包含的方法、实现的接口
2.3 hashCode()
// 返回当前对象的哈希码
public native int hashCode();
- 功能描述:
hashCode()
方法的主要功能是为对象提供一个哈希码值,这个值主要被用于支持哈希表的使用,例如在 Java 集合框架中广泛使用的HashMap
- 哈希码是根据对象的内部状态计算出的一个整数,用于确定对象在哈希表中的位置,以实现快速的查找、插入和删除操作
- 约定和规则:
- 重复调用一致性:对同一个对象多次调用
hashCode()
方法,必须始终返回相同的整数,前提是对象用于equals
比较的信息没有被修改 - 等价对象的哈希码相等:为了确保对象能在哈希表中正确存储和检索,规定如果两个对象通过
equals(Object)
方法判断相等,那么这两个对象调用hashCode()
必须返回相同的整数值 - 不等对象的哈希码优化:为了提高哈希表的性能,最好为不同的对象生成不同的哈希码,减少哈希碰撞,从而优化数据结构的性能
- 重复调用一致性:对同一个对象多次调用
2.4 equals(Object obj)
// 判断两个对象是否等价
// 默认的实现只简单地比较两个对象的引用是否相同
public boolean equals(Object obj) {
return (this == obj);
}
- 功能描述:
equals()
方法用于确定调用对象与作为参数传递的对象(obj
)是否 “相等”Object
类的实现中,仅当两个对象引用指向同一内存地址时,才认为它们相等
- 等价关系规则:
- 自反性:对于任何非空引用值
x
,x.equals(x)
必须返回true
- 对称性:对于任何非空引用值
x
和y
,x.equals(y)
应当仅在y.equals(x)
也返回true
时返回true
- 传递性:如果
x.equals(y)
和y.equals(z)
都返回true
,则x.equals(z)
也应返回true
- 一致性:只要对象
x
和y
的等价比较中用到的信息没有改变,反复调用x.equals(y)
应当始终返回同样的结果 - 对 null 的比较:对于任何非空引用值
x
,x.equals(null)
应当返回false
- 自反性:对于任何非空引用值
- 实现注意事项:
- 在自定义类中重写
equals()
方法时,应确保遵守上述等价关系的规则 - 当重写
equals()
方法时,也应该重写hashCode()
方法,以保持hashCode()
的一般约定,即相等的对象必须具有相等的哈希码
- 在自定义类中重写
- 方法作用:
- Java 中,不同于基本数据类型的比较(使用
==
),对象比较更复杂,因为对象的 “等价性” 可以根据实际需求定义(如属性值相等),equals()
方法提供了一种标准方式来定义两个对象是否等价 - 在 Java 的集合框架中,例如
HashMap
和HashSet
,对象是否相等直接影响到对象的存储和检索,正确实现equals()
方法是使用这些集合的前提
- Java 中,不同于基本数据类型的比较(使用
2.5 clone()
// 浅拷贝,使用时往往需要重写为 public 形式
// 注意: 要求被克隆的对象所属的类实现 Cloneable 接口
protected native Object clone() throws CloneNotSupportedException;
-
功能描述:
clone()
方法的目的是创建一个新的对象,这个新对象在逻辑上与原始对象相等,但在内存中占用不同的位置(即x.clone() != x
总是为真)- 它实现了对象的浅拷贝,即拷贝对象及其非静态字段,但不递归复制字段指向的对象
-
浅拷贝 VS 深拷贝:
- 浅拷贝:默认的
clone()
方法实现是浅拷贝,意味着对象的非对象字段(如基本数据类型字段)会被完全复制,但对象字段仅复制引用,不复制引用的对象本身 - 深拷贝:需要手动实现,在
clone()
方法中逐个复制对象内部所有可变的引用类型字段。通常涉及到修改super.clone()
返回的对象的一个或多个字段,确保所有内部的复杂结构都得到适当的复制
- 浅拷贝:默认的
-
使用限制:Java 要求任何使用
clone()
方法的类必须实现Cloneable
接口。这个接口是一个标记接口,不包含任何方法,其目的是表明类的设计者已经考虑到了复制问题,并且该类支持进行字段内容的复制- 如果一个类没有实现
Cloneable
接口而调用clone()
方法,将抛出CloneNotSupportedException
异常
- 如果一个类没有实现
-
特殊情况-数组:
- 所有的数组类型都被视为实现了
Cloneable
接口,因此数组总是可以被克隆,数组类型的clone()
方法返回的类型也是相同的数组类型 (T[]
)
- 所有的数组类型都被视为实现了
2.6 toString()
// 生成对象的字符串表示,往往需要重写
// 默认的实现是将类名与对象的哈希码以十六进制形式结合起来
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
toString()
方法提供了一种将对象转换为人类可读的字符串形式的方式,对于调试和日志记录非常有用,建议所有子类重写此方法
2.7 notify()
// 随机唤醒某个具有相同锁的对象从 wait 状态进入争锁状态
// 方法必须在同步方法或同步块中被调用,以确保调用它的线程持有当前对象的锁
public final native void notify();
-
notify()
方法的核心功能是在多线程环境中管理线程的等待与唤醒机制。它被用来唤醒一个在此对象上调用wait()
方法而进入等待状态的线程 -
随机唤醒:当多个线程在某个对象的锁上调用
wait()
方法后进入等待状态时,notify()
方法可以从这些等待的线程中随机选择一个线程,使其从等待状态中返回,准备继续执行 -
线程状态变化:
- 被唤醒的线程仍需竞争对象锁。只有当前持有锁的线程释放锁后,被唤醒的线程才有机会获取锁并继续执行
- 唤醒并不意味着立即执行。被唤醒的线程将重新参与竞争该对象锁,其竞争的条件与其他可能正在等待锁的线程无异
-
调用条件:
notify()
方法必须由锁的当前持有者调用,即在对象的同步方法或同步块中使用。如果一个线程试图在未持有对象锁的情况下调用notify()
,会抛出IllegalMonitorStateException
-
线程如何成为锁的当前持有者:
- 对象的同步实例方法:锁定调用该方法的对象实例
public synchronized void synchronizedMethod() { // ... }
- 同步语句块:可以指定锁定任何对象,包括
this
(当前实例)、类对象或任何其他对象
public void someMethod() { synchronized (this) { // ... } }
- 类的同步静态方法:锁定的是类的
Class
对象,而非类的某个实例
public static synchronized void synchronizedStaticMethod() { // ... }
2.8 notifyAll()
// 唤醒所有具有相同锁的对象从 wait 状态进入争锁状态
// 方法必须在同步方法或同步块中被调用,以确保调用它的线程持有当前对象的锁
public final native void notifyAll();
- 全面唤醒:相比
notify()
随机唤醒单个线程,notifyAll()
确保所有等待的线程都能得到处理机会 - 竞争公平:所有被唤醒的线程将与其他可能正在尝试同步此对象的线程一起,公平地竞争获取对象锁
- 使用条件与规范:
notifyAll()
方法必须由锁的当前持有者调用。如果在未持有对象锁的情况下调用,将抛出IllegalMonitorStateException
,因为只有锁的持有者才能安全地修改等待条件,并通知所有等待线程
2.9 wait
2.9.1 wait(long timeout)
// 等待 timeout 毫秒之后自动醒来,或者靠唤醒(释放锁)
public final native void wait(long timeout) throws InterruptedException;
wait(long timeout)
方法使当前线程暂停并释放它持有的对象锁,进入等待状态,直到发生以下事件之一:- 被通知: 如果有其他线程对此对象调用了
notify()
或notifyAll()
,当前线程可能被唤醒 - 超时: 如果指定的等待时间结束,当前线程会自动唤醒
- 被中断: 如果当前线程在等待期间被中断,它会抛出
InterruptedException
- 被通知: 如果有其他线程对此对象调用了
- 虚假唤醒:指线程可能在没有任何明确通知的情况下唤醒。为了处理这种情况,等待通常应该放在一个循环中,以确保等待的条件确实得到了满足
- 代码示例:
Thread consumerThread = new Thread(() -> {
synchronized (sharedResource) {
while (!dataReady) {
try {
System.out.println("消费者正在等待数据准备好...");
// 如果在5000毫秒内生产者准备数据并调用 notify(), 它将被唤醒; 否则, 它将因超时而唤醒
sharedResource.wait(5000); // 等待通知或超时
if (dataReady) {
System.out.println("消费者拿到数据!");
} else {
System.out.println("消费者超时! 无可用数据");
}
} catch (InterruptedException e) {
System.out.println("消费者被中断");
}
}
}
});
2.9.2 wait(long timeout, int nanos)
// 至少等待 timeout 毫秒,nanos 是一个纳秒级的附加时间,用来微调 timeout 参数
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
// 检查传入的 timeout 参数是否小于0,如果是,则抛出 IllegalArgumentException 异常,因为时间不能是负数
throw new IllegalArgumentException("timeout value is negative");
}
// 检查 nanos 参数是否在0到999999纳秒之间,如果不是,则抛出 IllegalArgumentException 异常,因为纳秒值必须在这个范围内
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
// 调整超时时间: 如果纳秒参数大于0,则将毫秒超时时间 timeout 加 1,因为在 Java 中,等待时间的最小单位是毫秒,任何非零的纳秒数都至少会导致等待时间增加1毫秒
if (nanos > 0) {
timeout++;
}
// 调用重载的 wait(long timeout) 方法,传入调整后的毫秒数
wait(timeout);
}
- 功能描述:
wait(long timeout, int nanos)
方法使得当前线程可以在释放对象锁的同时等待至多timeout
毫秒加nanos
纳秒。如果在此期间:- 被通知: 如果有其他线程对此对象调用了
notify()
或notifyAll()
,当前线程可能被唤醒 - 超时: 如果指定的等待时间结束,当前线程会自动唤醒
- 被中断: 如果当前线程在等待期间被中断,它会抛出
InterruptedException
- 被通知: 如果有其他线程对此对象调用了
- 方法作用:提供了比毫秒更细的时间控制单位,即纳秒,对于需要极高时间精度的应用场景非常重要
2.9.3 wait()
// 永不超时,需要靠唤醒(释放锁)
public final void wait() throws InterruptedException {
wait(0);
}
wait()
方法调用wait(long timeout)
方法并传递0
作为参数,表示允许当前线程放弃对象锁并无限期地等待,直到其他线程对此对象调用notify()
或notifyAll()
方法唤醒它,或者当前线程被中断
2.10 finalize()
// 对象在被 GC 回收后执行的清理操作, 可能会引发 OOM(内存泄漏或内存溢出),建议使用 Java 9 引入的一个工具类 java.lang.ref.Cleaner 替代
protected void finalize() throws Throwable { }
- 在 Java 早期,
finalize()
是处理对象销毁前资源释放的主要方式,它允许在对象被垃圾收集器回收之前执行清理操作 - 理论上,
finalize()
方法中可以采取措施使对象再次可用(例如,重新赋予引用),但通常不推荐,因为会导致对象回收行为变得不可预测 - 从 Java 9 开始,
finalize()
方法已被标记为过时,因为它可能导致不稳定和效率低下的代码
3. 总结
3.1 方法概览
getClass()
:返回对象的运行时类。提供了动态获取类信息的能力,是 Java 反射机制的基础之一hashCode()
:返回对象的哈希码,主要用于哈希表(如HashMap
)中。它需要与equals()
方法保持一致,即相等的对象必须有相同的哈希码equals(Object obj)
:检测某个对象是否等于当前对象。通常需要在自定义类中重写以实现逻辑上的 “相等”clone()
:创建并返回此对象的一个副本。默认行为是浅拷贝,但可以被重写实现深拷贝toString()
:返回对象的字符串表示,通常包含类名和哈希码的无符号十六进制表示。通常被重写以提供更多的实例信息notify()
和notifyAll()
:用于唤醒在此对象监视器上等待的单个线程或所有线程。这些方法必须在同步块内调用,且调用者必须是对象锁的当前持有者wait()
方法族(wait()
,wait(long timeout)
,wait(long timeout, int nanos)
:使当前线程放弃对象锁并进入等待状态,直到被通知(notify
/notifyAll
)或中断。用于线程间的协调和通信finalize()
:在垃圾收集器回收对象之前调用,用于清理资源。已被废弃并不推荐使用,因为它不可预测、效率低下且容易出错。建议使用try-with-resources
或Cleaner
类
3.2 归纳
- 线程同步与通信:
wait()
,notify()
, 和notifyAll()
是 Java 中实现线程间同步和通信的基本机制。这些方法在使用时需要特别注意同步块的设计,以避免死锁或过早通知等问题 - 对象克隆:
clone()
方法提供了对象复制的能力,但需要注意浅拷贝与深拷贝的区别及其对应用的影响 - 哈希与等价:
hashCode()
和equals()
方法定义了对象哈希存储和等价比较的标准,重写这些方法时必须遵循一定的约定,以保证它们的一致性