Object类是Java中所有类的父类,所以对其的认识是十分重要的。
下面是我对Object类中个方法的认识(结合了代码中的注释以及看过的一些书)
public class Object {
//其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。函数的执行是在静态代码块中执行的,在类首次进行加载的时候执行。
private static native void registerNatives();
static {
registerNatives();
}
//返回此Object的运行时对象,返回的class对象由synchronized锁定
public final native Class<?> getClass();
/**
* 特点:
* 1、每当在执行Java应用程序期间多次在同一对象上调用它时,{@code hashCode}方法必须始终
* 返回相同的整数,前提是不修改对象上的{@code equals}比较中使用的信息。 从应用程序的一
* 次执行到同一应用程序的另一次执行,该整数不需要保持一致。
* 2、如果两个object根据equals判定真,则hashcode必须返回相同的值
* 3、如果两个object不equal,hashcode可以相同也可以不同。
*/
public native int hashCode();
/**
* Object的equals()方法比较的是对象在内存中的地址。
* 对于所有的非空引用:
* 自反性:x.equals(x)为true
* 对称性:如果x.equals(y)为true,则y.equals(x)为true
* 传递性:如果x.equals(y)为true,y.equals(z)为true,则x.equals(z)为true
* 一致性:对于任何非null的引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或者始终返回false
* 对于任何非空引用值x,x.equal(null)应返回false
*/
public boolean equals(Object obj) {
return (this == obj);
}
/**
* 创建并且返回一个对象的复制,clone()的正确调用是需要实现Cloneable接口,如果没有实现Cloneable接口,并且子类直接
* 调用Object类的clone()方法,则会抛出CloneNotSupportedException异常。
* x.clone() != x
* x.clone().getClass() == x.getClass();
* x.clone().equals(x);
* 这些表达式一般都为true,但是并不是绝对需要的。
* 一般而言,clone()是将这个对象复制一次,变成两个对象,都有着自己的内存空间,而不是只建立一个引用,所以x.clone() != x;为true
* x.clone().getClass() == x.getClass();为true说明两个对象的运行时对象相同
* x.clone.equals(x);依赖于equals()方法的重写,如果只是调用Object的equals()方法,那么这个表达式返回false,因为Object的equals()
* 方法比较的是对象的地址。
*
* 深拷贝和浅拷贝:
* 浅拷贝是将原始对象中的数据型字段拷贝到新对象中去,将引用型字段的“引用”复制到新对象中去,
* 不把“引用的对象”复制进去,所以原始对象和新对象引用同一对象,新对象中的引用型字段发生变化会
* 导致原始对象中的对应字段也发生变化。深拷贝是在引用方面不同,深拷贝就是创建一个新的和原始字
* 段的内容相同的字段,是两个一样大的数据段,所以两者的引用是不同的,之后的新对象中的引用型字
* 段发生改变,不会引起原始对象中的字段发生改变。
*
*/
protected native Object clone() throws CloneNotSupportedException;
//返回类的名称加上@,然后 加上此类的哈希码的16进制表示
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
/**
* 唤醒正在此对象上等待的一个线程。 如果有多个线程正在等待此对象,则选择其中一个线程被唤醒。 选择是任意的,由JVM实施决定。
* 线程通过调用{@code wait}方法等待获取对象。
* 在当前线程放弃对该对象的锁定之前,唤醒的线程(就绪状态)将无法继续。唤醒的线程将以普通的方式与可能正在竞争同步此对象的任何其他线程
* 竞争; 例如,唤醒线程在成为锁定此对象的下一个线程时没有可靠的特权或劣势。
* 这个方法只能被对象的所有者线程调用,线程以以下三种方式成为对象的所有者:
* 1、通过执行该对象的同步实例方法
* 2、通过执行该对象的同步代码块
* 3、通过执行该对象的静态同步方法。
* 同一时刻只能有一个线程持有该对象的锁
*/
public final native void notify();
//使所有正在等待池中等待同一共享资源的全部线程从等待状态退出,进入可运行状态,让它们竞争对象的锁,只有获得锁的线程才能进入就绪状态
public final native void notifyAll();
/**
* 使当前线程进入等待状态,直至其他线程调用notify()或者notifyAll()方法,又或是达到指定时间
* 当前线程必须持有该对象的锁。
* 这个方法使得当前线程T 进入该对象的等待队列中,直到以下四件事情发生:
* 1、有其他线程调用了notify()方法,且线程T正好被选为唤醒的线程
* 2、有其他线程调用了notifyAll()方法
* 3、其他线程调用了interrupt()方法中断线程T
* 4、等待的时间超过了wait制定的时间。但是,如果{@code timeout}为零,则不考虑实时,线程只是等待其他线程唤醒。
* 线程被唤醒后就从Object的等待集合中删除,并且能够被重新调度
* 同时JDK中说明了线程可以在以上四种情况之外被唤醒,这种情况称为“伪唤醒”。
* 解决方法是将判断条件放入while循环中:
* synchronized(obj){
* while(condition){ obj.wait(); }
* }
* Never invoke wait outside a loop!!
*/
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
timeout++;
}
wait(timeout);
}
//不考虑等待时间,线程只等待其他线程唤醒
public final void wait() throws InterruptedException {
wait(0);
}
/**
* 当垃圾回收器认为一个对象没有其他引用时,调用该方法。子类重写{@code finalize}方法以处理系统资源或执行其他清理。
* 《深入理解Java虚拟机》书中谈到:
* 即使在可达性分析算法中不可达的对象,也并非是“非死不可”的。要真正宣告一个对象死亡,至少要经历两次标记过程:
* 1、如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的
* 条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用
* 过,则虚拟机认为该对象死亡。
* 2、如果需要执行finalize()方法,那么对象会被放置到一个F-Queue的队列中。finalize()方法是对象逃脱死亡的最后一次
* 机会。GC对F-Queue中的对象进行第二次标记,如果对象在finalize()方法中成功拯救了自己,则GC在第二次标记时将对象移出
* “即将回收”集合;如果对象没有逃脱,那就真的GG了
* 注意 finalize()方法只能被系统调用一次。如果对象面临第二次垃圾回收,finalize()方法不会被执行。
*/
protected void finalize() throws Throwable { }
}
关于线程的伪唤醒,网上找了很多资料,感觉都只是谈及What和How,没有谈及Why。说实话我也不清楚为什么会出现伪唤醒。
有一篇博客是这么说的:在多核处理器下,signal(notify)可能会激活多余一个线程(阻塞在条件变量上的线程)。结果是,当一个线程调用signal(notify)后,多个调用wait 的线程返回。原文链接