java Object类
java Object类
概要
java.lang.Object类是Java当中所有类的基类,即所有类的父类,它里面描述的所有方法,子类都可以使用。在对象实例化的时候,最终找的父类就是Object。
Object包含了9大常用方法: clone()、getClass()、finalize()、toString()、equals()、hashcode()、wait()、notify()、notifyAll()
源码如下:
1 public class Object { 2 3 // 注册本地方法 4 private static native void registerNatives(); 5 static { 6 // 静态初始化块,调用注册方法 7 registerNatives(); 8 } 9 10 // 返回运行时类的 Class 对象 11 public final native Class<?> getClass(); 12 13 // 返回对象的哈希码 14 public native int hashCode(); 15 16 // 比较当前对象与另一个对象的相等性 17 public boolean equals(Object obj) { 18 return (this == obj); // 默认实现比较内存地址 19 } 20 21 // 返回对象的字符串表示 22 public String toString() { 23 return getClass().getName() + "@" + Integer.toHexString(hashCode()); 24 } 25 26 // 克隆当前对象 27 protected native Object clone() throws CloneNotSupportedException; 28 29 // 唤醒单个线程 30 public final native void notify(); 31 32 // 唤醒所有等待线程 33 public final native void notifyAll(); 34 35 // 等待指定时间 36 public final native void wait(long timeout) throws InterruptedException; 37 38 // 无限期等待 39 public final native void wait() throws InterruptedException; 40 41 // 等待指定时间和纳秒 42 public final native void wait(long timeout, int nanos) throws InterruptedException; 43 44 // 在垃圾回收器确定不存在对对象的引用时调用 45 protected void finalize() throws Throwable { 46 // 可用于资源释放等清理工作 47 } 48 }
一、Object的九个方法
1. getClass()
public final native Class<?> getClass();
说明:
1)获取对象的运行时 class 对象,class 对象就是描述对象所属类的对象。用于获取该类的相关信息,比如获取该类的构造方法、该类有哪些方法、该类有哪些成员变量等信息。
2)这个方法通常是和 Java 反射机制搭配使用的。
3)不同VM针对class做了不同的优化,所以getClass()的实现也并不相同。
2. finalize()
protected void finalize() throws Throwable { }
主要用于在 GC 的时候再次被调用,如果实现了这个方法,对象可能在这个方法中再次复活,从而避免被 GC 回收。
说明:
1) 在发生GC时触发该方法:该方法的大致流程,是当对象变成GC Roots不可达时,GC判断该对象是否覆盖了finalize()方法,若未覆盖,则直接将其回收;否则,若对象未执行过finalize()方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize()方法。
2) 执行finalize()方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收;否则,对象“复活”。
3) 在垃圾回收器确定对象没有任何引用时调用。可以在此方法中执行资源释放或清理工作,但不应依赖于此,因为它的调用时机不可预测。
3. toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
用于返回对象的字符串表示。默认返回格式如下:对象的 class 名称 + @ + hashCode 的十六进制字符串。
说明:通常,开发者会重写 toString() 方法,以提供更具描述性的对象信息。这对于调试和日志记录特别有用。
举个例子:
1 public class Person { 2 private String name; 3 private int age; 4 5 @Override 6 public String toString() { 7 return "Person{name='" + name + "', age=" + age + "}"; 8 } 9 }
4. equals()
public boolean equals(Object obj) {
return (this == obj);
}
该方法用于比较两个对象,如果这两个对象引用指向的是同一个对象,那么返回 true,否则返回 false。一般 equals 和 == 是不一样的,但是在 Object 中两者是一样的。子类一般都要重写这个方法。
注意:
1)重写了equals() 还需要重写hashcode()
2)如果两个对象equals()方法相等,则它们的hashCode返回值一定要相同。
3)如果两个对象的hashCode返回值相同,但它们的equals()方法却有可能最终返回false,这种情况叫做hash碰撞。
5. hashcode()
public native int hashCode();
说明:该方法主要用于获取对象的散列值。Object 中该方法默认返回的是对象的堆内存地址。
6. clone()
protected native Object clone() throws CloneNotSupportedException;
该方法是保护方法,实现对象的浅复制,只有实现了 Cloneable 接口才可以调用该方法,否则抛出 CloneNotSupportedException 异常。默认的 clone 方法是浅拷贝。所谓浅拷贝,指的是对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存。深拷贝则是会连引用的对象也重新创建。
7. wait()
wait 方法有三个重载版本,分别用于不同的等待需求
1 // 执行这个方法后,持有此对象监视器的线程会进入等待队列,同时释放锁 2 // 如果不在synchronized修饰的方法或代码块里调用,则会抛出IllegalMonitorStateException 异常 3 // 如果当前线程在等待时被中断,则抛出InterruptedException异常 4 public final void wait() throws InterruptedException { 5 wait(0); 6 } 7 8 // timeout是线程等待时间,时间结束则自动唤醒,单位ms 9 // Java默认的实现方式,native实现 10 public final native void wait(long timeout) throws InterruptedException; 11 12 // nanos是更精确的线程等待时间,单位ns(1 ms = 1,000,000 ns) 13 // Java默认的实现方式 14 public final void wait(long timeout, int nanos) throws InterruptedException { 15 if (timeout < 0) { 16 throw new IllegalArgumentException("timeout value is negative"); 17 } 18 if (nanos < 0 || nanos > 999999) { 19 throw new IllegalArgumentException( 20 "nanosecond timeout value out of range"); 21 } 22 if (nanos > 0) { 23 timeout++; 24 } 25 wait(timeout); 26 }
wait() 持有此对象监视器的线程会进入等待队列,同时释放锁,允许其他线程访问该对象。如果没有唤醒,线程会一直等待,不会自动超时。
wait(long timeout) 设定一个超时间隔,如果在 timeout (毫秒) 指定的时间内没有被唤醒,线程将自动返回继续执行
wait(long timeout, int nanos) 方法提供了一种灵活的方式来让线程等待,并能精确控制等待的时间,通过结合使用 timeout (毫秒) 和 nanos (额外的纳秒时间,范围在[0,999999]) ,可以实现更加细粒度的控制。这种精细化的控制对于某些对时间敏感的应用场景尤为重要。
说明:
1)调用 wait 方法的线程必须持有该对象的监视器锁。当线程调用wait() 时,它会释放锁并进入等待状态。
2)wait 方法用于使当前线程等待,直到其他线程调用此对象的notify() 或 notifyAll() 方法,或者等待的时间超时。
3)如果当前线程在等待时被中断,则抛出InterruptedException异常
8. notify()
主要用于唤醒在该对象上等待的某个线程。
9. notifyAll()
主要用于唤醒在该对象上等待的所有线程。
二、等待通知机制
要真正理解wait()、notify()、notifyAll()这三个方法,还需要了解等待队列。
1. 等待队列
每一个Object对象都包含一个等待队列,当对象的wait方法执行后,调用该方法的线程将会处于阻塞状态,持有此对象的监视器的线程进入等待队列,就像是线程的“休息室”。
与wait方法一样,执行notify方法的线程也必须提前获取锁。需要注意的是,被notify方法唤醒的线程并不会立即执行,因为要等调用notify方法的线程释放锁之后才会获取到锁。
那么问题来了:持有锁的线程执行了notify之后,要是等待队列中不只有一个线程,哪个线程将会获取锁呢?
解决办法:对于不同Java虚拟机,会有不同的处理方式。有的Java虚拟机(VM)会选择最先调用wait方法的线程,也有的则会随机选择一个线程。尽管notify方法的处理速度比notifyAll方法更快,但使用notifyAll方法更为稳妥。
2. 等待-通知机制
等待-通知的机制可以大概描述为: 处理任务的时间是不确定的,无法给出及时反馈,需要等待通知唤醒。
重要注意事项:
1)wait()、notify()和notifyAll() 必须在 synchronized 修饰的方法或代码块中使用。
2)在while循环里而不是if语句下使用wait(),确保在线程睡眠前后都检查wait()触发的条件(防止虚假唤醒)。
3)wait()方法必须在多线程共享的对象上调用。
3. 等待-通知机制的实际应用
1) 生产者/消费者模型
在开发中,wait-notify机制的最广泛用途就是实现生产者/消费者模型(Producer-Consumer)
- 生产者/消费者模型能解决绝大多数并发问题,通过平衡生产线程和消费线程的工作能力,来提高程序的整体处理数据的速度。
- 生产者线程和消费者线程的处理速度差异,会引起消费者想要获取数据时,数据还没生成或者生产者想要交付数据,却没有消费者接收的问题。
对于这样的问题,生产者/消费者模型可以消除这个差异。
举个例子:
1 // 生产者,有详细的注释 2 public class Producer implements Runnable{ 3 private Queue<Integer> queue; 4 private int maxSize; 5 public Producer(Queue<Integer> queue, int maxSize){ 6 this.queue = queue; 7 this.maxSize = maxSize; 8 } 9 10 @Override 11 public void run() { 12 // 这里为了方便演示做了一个死循环,现实开发中不要这样搞 13 while (true){ 14 //(1)wait()、notify()和notifyAll()必须在synchronized修饰的方法或代码块中使用 15 synchronized (queue){ 16 //(2)在while循环里而不是if语句下使用wait(),确保在线程睡眠前后都检查wait()触发的条件(防止虚假唤醒) 17 while (queue.size() == maxSize){ 18 try{ 19 System.out.println("Queue is Full"); 20 // 生产者线程进入等待状态,在此对象监视器上等待的所有线程(其实只有那个消费者线程)开始争夺锁 21 queue.wait(); 22 }catch (InterruptedException ie){ 23 ie.printStackTrace(); 24 } 25 } 26 Random random = new Random(); 27 int i = random.nextInt(); 28 System.out.println("Produce " + i); 29 queue.add(i); 30 // 唤醒这个Queue对象的等待池中的所有线程(其实只有那个消费者线程),等待获取对象监视器 31 queue.notifyAll(); 32 } 33 } 34 } 35 }
2) 实现方式
在 Java 语言里,等待- 通知机制可以有多种实现方式,比如 Java 语言内置的 synchronized 配合 wait()、notify()、notifyAll() 这三个方法就能轻松实现。
这个等待队列和互斥锁是一对一的关系,每个互斥锁都有自己独立的等待队列。如下图:
除了刚才用synchronized关键字配合Object的wait/notify实现, 还可以用Lock接口配合Condition的await、signalAll实现,此外还可以 用BlockingQueue实现。
三、对象监视器
对象监视器是 Java 中用于实现同步的机制,主要涉及到线程对共享资源的访问控制。每个对象都有一个关联的监视器(也称为锁),当一个线程访问一个被 synchronized
修饰的方法或代码块时,它会获得该对象的监视器锁。
Java 中的对象监视器与 monitorenter 和 monitorexit 指令密切相关。这两个指令是 Java 虚拟机(JVM)在执行同步代码时使用的底层指令。它们与对象监视器的实现直接相关。
1. monitorenter
当线程进入一个被 synchronized 修饰的方法或代码块时,JVM 会执行 monitorenter 指令。
此指令会尝试获取对象的监视器锁。如果锁已经被其他线程占用,当前线程会被阻塞,直到能够获得锁。
2. monitorexit
当线程退出同步块(无论是正常结束还是因为异常)时,JVM 会执行 monitorexit 指令。
该指令会释放之前获取的监视器锁,使得其他等待该锁的线程能够获得执行权。
总结:使用 synchronized 关键字可以简化锁的管理,避免手动处理监视器锁的获取和释放,从而提高代码的可读性和安全性。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)