文章目录
引言
这是JDK源码阅读的第一节,我们来看看java.lang包下的Object类。Java中任何一个没有继承其他类的对象都默认继承Object类,从它开始JDK源码阅读一点都不为过。
类图
Object类中的方法可以分为几组:toString()、clone()、getClass()、finalize();hashCode()和equals();notify()、notifyAll()和wait()。
toString()
源码
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
这个方法很简单,就是将当前对象的类的名称和十六进制的hashCode用@符号拼接起来返回一个字符串,当我们输出一个对象时,如果要看到类的属性和属性值,就需要重写类的toString()方法。
clone()
源码
protected native Object clone() throws CloneNotSupportedException;
clone()也是一个native方法,它的作用是返回当前对象的一个备份。它的原理是先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容,对基本数据类型就是值复制,而对非基本类型变量保存的仅仅是对象的引用,这就是所谓的”浅拷贝“。
如果想要对非基本类型的对象也进行数据的克隆,即”深克隆“
有以下两种方式:
- 通过重写clone方法,除了调用父类的clone方法对基本数据类型进行复制外,采用set方法对对象进行复制
class A implements Cloneable{
private int i=1;
private B b=new B();
public void setB(B b){
this.b=b;
}
@Override
protected Object clone() throws CloneNotSupportedException {
A a = (A) super.clone();
a.setB((B) b.clone());
return a;
}
}
class B implements Cloneable{
}
注意:对于对象A而言已经达到深拷贝,但是如果对象B中存在非基本类型的属性,对象B也是浅拷贝。
- 通过序列化与反序列化可以达到对象的深克隆,将在后面的序列化章节讲到。
重点
使用clone()的对象必须实现Cloneable接口,否则会抛出CloneNotSupportedException。通过Cloneable的接口定义,发现这个接口没有任何内容,即”标记接口“,用来表明当前对象可以被克隆。
getClass()
源码
public final native Class<?> getClass();
这个方法是返回对象的Class对象,每个类都存在一个Class对象,用来保存类的信息,例如类的属性和方法等等,eclipse的代码提示功能就是调用了这个方法,同时反射机制也是用了Class对象,在后面的反射章节会讲到。
重点
除了native表示本地方法外,这里出现了另外一个关键字final,final可以用来修饰类、方法和属性
规则如下:
- final修饰的类不能被继承
- final修饰的方法不能被重写
- final修饰属性是常量,必须在定义的时候初始化,或者在static块中被初始化,且后面不允许对其值进行修改。
finalize()
源码
protected void finalize() throws Throwable { }
垃圾回收器准备释放内存的时候,会先调用finalize()方法,如果程序中需要进行一些特殊的收尾工作,可以重写该方法。
hashCode()和equals()
源码
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
native关键字,表明这是一个本地方法,即采用C/C++编写,具体的底层实现不是本篇文档的目的,只需要了解hashCode()方法返回的是对象的hash码
equals()方法用来比较两个对象是否相等,默认表示内存地址相同
重点
在一些对象的比较中,我们可能需要重写equals()方法,根据需求判断两个对象是否相等,根据Java规范,重写equals()方法时要重写hashCode()方法
规则:
- 当hashcode不等时,equals()一定不相等;当hashcode相等时,equals()不一定相等
- 当equals()不等时,hashcode不一定不等;当equals()相等时,hashcode一定相等
提示:出现上述场景的原因是存在"哈希冲突",这个知识点在后面的Map集合中会讲到。
notify()、notifyAll()和wait()
源码
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
wait()方法会阻塞调用的线程直到超时或者调用notify()或者notifyAll()方法
notify()和notifyAll()方法会唤醒被wait()方法阻塞的线程,前者会随机唤醒其中一个线程,后者将会唤醒所有线程
重点
这三个方法使用的前提都是要获得对象锁,然后才能调用对象的该方法,否则会抛出IllegalMonitorStateException异常,通常会与synchronized关键字联合起来使用。
线程T先获得对象o的同步锁,调用对象o的wait()方法,阻塞线程T,释放同步锁,当线程X获得对象o的同步锁,然后调用对象o的notify()方法时,线程T就被唤醒了,但是要等到线程X退出synchronized块,释放锁之后,线程T才能再次获得锁,继续执行直到退出synchronized块后释放锁。
具体的底层原理涉及到Java中的monitor机制
Java中的monitor机制
在操作系统层面,为了控制进程间的同步操作,提供了同步原语,semaphore(信号量)和mutex(互斥量),但是这个仅仅是达到了互斥效果,对于同步的控制需要人为的进行加锁和解锁,即semaphore中的PV操作,如果顺序出错或者操作出错,可能会造成死锁的问题。因此提供了更高一层的操作,即monitor,在同一时刻,只有一个进程/线程进入临界区,达到互斥的效果,同时需要提供阻塞和唤醒的API,不同的编程语言的实现机制不同。
monitor机制的三个部分
- 临界区
- monitor对象和锁
- 阻塞和唤醒操作
在java中synchronized关键字用来标识了临界区,synchronized关联的对象就充当了锁,同时Object类提供了wait()、notify()和notifyAll()操作。
ObjectMonitor模式
Java中的每个对象都有一把锁,一个入口队列(Entry Set)和一个阻塞队列(Wait Set),线程首先进入该对象的入口队列(1),当获得了该对象的锁之后,成为所有者(2),当失去锁时进入该对象的阻塞队列(3),直到被唤醒后进入该对象的入口队列(4),再次获得锁后执行完成后释放锁(5)。