JDK源码阅读(一):Object类

引言

这是JDK源码阅读的第一节,我们来看看java.lang包下的Object类。Java中任何一个没有继承其他类的对象都默认继承Object类,从它开始JDK源码阅读一点都不为过。

类图

Object类图
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方法,它的作用是返回当前对象的一个备份。它的原理是先在内存中开辟一块和原始对象一样的空间,然后原样拷贝原始对象中的内容,对基本数据类型就是值复制,而对非基本类型变量保存的仅仅是对象的引用,这就是所谓的”浅拷贝“。

浅拷贝
如果想要对非基本类型的对象也进行数据的克隆,即”深克隆
深拷贝
有以下两种方式:

  1. 通过重写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也是浅拷贝

  1. 通过序列化与反序列化可以达到对象的深克隆,将在后面的序列化章节讲到。

重点

使用clone()的对象必须实现Cloneable接口,否则会抛出CloneNotSupportedException。通过Cloneable的接口定义,发现这个接口没有任何内容,即”标记接口“,用来表明当前对象可以被克隆。

getClass()

源码

public final native Class<?> getClass();

这个方法是返回对象的Class对象,每个类都存在一个Class对象,用来保存类的信息,例如类的属性和方法等等,eclipse的代码提示功能就是调用了这个方法,同时反射机制也是用了Class对象,在后面的反射章节会讲到。

重点

除了native表示本地方法外,这里出现了另外一个关键字finalfinal可以用来修饰类、方法和属性
规则如下:

  1. final修饰的类不能被继承
  2. final修饰的方法不能被重写
  3. 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()方法
规则:

  1. 当hashcode不等时,equals()一定不相等;当hashcode相等时,equals()不一定相等
  2. 当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机制的三个部分

  1. 临界区
  2. monitor对象和锁
  3. 阻塞和唤醒操作

在java中synchronized关键字用来标识了临界区,synchronized关联的对象就充当了锁,同时Object类提供了wait()、notify()和notifyAll()操作。

ObjectMonitor模式

在这里插入图片描述

Java中的每个对象都有一把锁,一个入口队列(Entry Set)和一个阻塞队列(Wait Set),线程首先进入该对象的入口队列(1),当获得了该对象的锁之后,成为所有者(2),当失去锁时进入该对象的阻塞队列(3),直到被唤醒后进入该对象的入口队列(4),再次获得锁后执行完成后释放锁(5)。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值