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 关键字可以简化锁的管理,避免手动处理监视器锁的获取和释放,从而提高代码的可读性和安全性。

posted @ 2024-03-04 11:56  欢乐豆123  阅读(7)  评论(0编辑  收藏  举报