阅读JDK源码也是一件非常重要的事情,尤其是使用频率最高的一些类,通过源码可以清晰的清楚其内部机制。
如何阅读jdk源码(基于java8)?
首先找到本地电脑中的jdk安装路径,例如我的就是E:\jdk,
src.zip中就包含了jdk的所有源码,并将相关源码导入到ideal、eclipse等等,如图:
第一步就来介绍所有类的基类Object,先看下此类的架构图:
一、registerNatives
private static native void registerNatives();
static {
registerNatives();
}
在Object被导入的时候,就会调用此方法,该方法是使用native关键字修饰,也就是说调用的是本地方法。
native解释:
Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI(Java Native Interface)接口调用其他语言(例如C、C++等等)来实现对底层的访问。 JNI是Java本机接口,是一个本机编程接口,它是JDK的一部分。JNI允许Java代码
使用以其他语言编写的代码和代码库。InvocationAPI(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用Java代码。
registerNatives:
在类初始化时调用registerNatives()方法进行本地方法的注册,也就是初始化java原生方法(native修饰)映射到对应的其他语言描述方法,比如c语言的方法。我们可以发现用native修饰的方法都没有具体的方法体(类似于抽象方法),
因为不需要java来实现,是使用其他语言实现的,直接调用即可。
说白了,就是将程序中所有native修饰的方法与其他语言描述的方法对应上。
二、getClass()
public final native Class<?> getClass();
作用:获取某个对象运行时的类对象(class对象)
三、hashCode()、equals()、clone()、toString()
1、hashCode()
一般hashCode()不单独使用,功能是在比较俩个对象是否相等的时候提高比较效率,比如在一些hashSet、hashTable等数据结构中,不允许存在相同的元素,所以每次插入元素都要和已经存在的元素依次进行比较,如果依次对每个元素调用equals(Object obj),
对于大量数据来说太慢了,hashCode()便是在哈希表结构中提高比较速率的利器,如果俩个元素的hashCode()不同,则equals(Object obj)就不必比较了,因为肯定不同;如果俩个元素的hashCode()相同,则再比较equals(Object obj)于是我们得出了一个结论:hashCode()相同,equals(Object obj)不一定相同;hashCode()不同,equals(Object obj)一定不同;equals(Object obj)相同,hashCode()一点相同;
但是有一个前提,必须同时重写equals(Object obj)和hashCode()。
原则:
- 1. 在java程序运行期间,若用于equals方法的信息或者数据没有修改,name同一个对象多次调用此方法,返回的哈希码是相同的。而在两次独立的运行java程序时,对于同一对象,不需要返回的哈希码相同
- 2. 如果根据equals方法,两个对象相同,则这两个对象的哈希码一定相同
- 3. 假如两个对象通过equals方法比较不相同,那么这两个对象调用hashCode也不是要一定不同,相同也是可以的。但是使用者应该知道对不同的对象产生不同的hashCode是可以提高hash tables的性能的。
2、equals()
此方法比较的是两个对象的内存地址是否一致。如果不重写equals()方法,equals()与==的意义一致。
public native int hashCode(); public boolean equals(Object obj) {
return (this == obj);
}
3、clone()
protected native Object clone() throws CloneNotSupportedException;
作用:返回一个克隆对象,满足以下原则:
返回一个此类的克隆对象,二者具有相同的属性,二者拥有独立的属性存储空间,分别位于堆内存中的俩快空间中,不相互影响,但是也不是绝对的,因为存在浅克隆和深克隆的区别
前提:如果一个类没有实现Cloneable接口(这个接口里面没有任何方法的声明,是一个标记接口),那么对此类的对象进行复制时,在运行时会出现CloneNotSupportedException异常。
clone涉及到一个很经典的问题就是深拷贝与浅拷贝,请看如下示例
浅拷贝:
class House {
private String addr;
public House(String addr) {
this.setAddr(addr);
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
} }
class Person implements Cloneable {
private String name;
private House house;
public Person(String name, House house) {
this.setName(name);
this.setHouse(house);
}
@Override
public Object clone() {
//浅拷贝
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public House getHouse() {
return house;
}
public void setHouse(House house) {
this.house = house;
} }
public class ShallowClone {
public static void main(String[] args) {
House house = new House("地址");
Person person = new Person("名字", house);
Person personClone = (Person) person.clone();
//这里我改变person对象中的house,可以看到personClone中的house也进行了变化
person.getHouse().setAddr("新地址");
System.out.println(personClone.getHouse().getAddr());
} }
深拷贝:
//对象具有拷贝功能必须先实现Cloneable接口,否则报错
class House implements Cloneable {
private String addr;
public House(String addr) {
this.setAddr(addr);
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
} }
class Person implements Cloneable {
private String name;
private House house;
public Person(String name, House house) {
this.setName(name);
this.setHouse(house);
}
@Override
public Object clone() {
Person p = null;
try {
p = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
//同时将成员变量house也拷贝一份,以实现深拷贝
p.house = (House) p.house.clone();
return p;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public House getHouse() {
return house;
}
public void setHouse(House house) {
this.house = house;
} }
public class DeepClone { public static void main(String[] args) throws CloneNotSupportedException {
House house = new House("地址");
Person person = new Person("名字", house);
Person personClone = (Person) person.clone();
//这里我改变person对象中的house,可以看到personClone中的house没有变化
person.getHouse().setAddr("新地址");
System.out.println(personClone.getHouse().getAddr());
} }
- 浅拷贝: 浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝: 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。
4、toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
返回类的名称(权限定名称)加上@,然后 加上此类的哈希码的16进制表示,例如:com.ll.client.House@2a139a55
四、notify()、notifyAll()、wait()
接触过多线程的应该对这几个方法非常熟悉了。
1、notify()
public final native void notify();
通知可能等待该对象的对象锁的其他线程。由JVM(与优先级无关)随机挑选一个处于wait状态的线程。
- - 在调用notify()之前,线程必须获得该对象的对象级别锁
- - 执行完notify()方法后,不会马上释放锁,要直到退出synchronized代码块,当前线程才会释放锁
- - notify()一次只随机通知一个线程进行唤醒
2、notifyAll()
public final native void notifyAll();
和notify()差不多,只不过是使所有正在等待池中等待同一共享资源的全部线程从等待状态退出,进入可运行状态
让它们竞争对象的锁,只有获得锁的线程才能进入就绪状态
每个锁对象有两个队列:就绪队列和阻塞队列
- - 就绪队列:存储将要获得锁的线程
- - 阻塞队列:存储被阻塞的线程
3、wait()
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 > 0) {
timeout++;
} wait(timeout);
} public final void wait() throws InterruptedException {
wait(0);
}
wait()和wait(long timeout, int nanos)都在在内部调用了wait(long timeout)方法,所以重点研究wait(long timeout)方法即可。
wait方法会引起当前线程阻塞,直到另外一个线程在对应的对象上调用notify或者notifyAll()方法,或者达到了方法参数中指定的时间。 调用wait方法的当前线程一定要拥有对象的监视器锁。
wait方法会把当前线程T放置在对应的object上的等待队列中,此时这个对象上的所有同步请求都不会得到该线程的响应。
在以下四件事发生之前,线程T一直处于休眠状态(线程T是在其代码中调用wait方法的那个线程)
- 当其他的线程在对应的对象上调用notify方法,而在此对象的对应的等待队列中将会任意选择一个线程进行唤醒。
- 其他的线程在此对象上调用了notifyAll方法
- 其他的线程调用了interrupt方法来中断线程T
- 等待的时间已经超过了wait中指定的时间。如果参数timeout的值为0,不是指真实的等待时间是0,而是线程等待直到被另外一个线程唤醒。