根据Java规范的规定,如果两个对象相等,那么它们的哈希值必须相等。也就是说如果 equals
方法判断两个对象是相等的,那这两个对象的 hashCode
值也要相等。如果重写 equals()
时没有重写 hashCode()
方法的话就可能会导致 equals
方法判断是相等的两个对象,hashCode
值却不相等。
例如hashset集合中插入的对象重写 equals()但是没有重写 hashCode() 就会导致插入重复的元素。
在 Java 中,与其他操作系统一样,线程是通过调度器来管理执行的。当一个进程中所有的非守护线程都执行完毕后,该进程会自动结束并终止所有守护线程。因此,如果在 main 线程中创建了一个新线程,main 线程结束之后,该新线程仍然会继续运行。
这是因为在Java中,除了守护线程以外,每个线程都是独立执行的,并且拥有自己独立的执行堆栈和执行上下文。在 main 线程中创建一个新线程后,该新线程会被 JVM 调度器分配 CPU 时间片,并独立运行。这时即使 main 线程结束,该新线程仍然可以继续运行,直到执行完毕或者被显式地终止。
需要注意的是,如果该新线程是守护线程,那么在所有非守护线程执行完毕后,JVM 会自动终止所有守护线程并退出进程。因此,如果该新线程是守护线程,则在 main 线程结束之后可能会被强制终止,而不会继续运行。
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:
start()
。start()
等待运行的状态。线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换
Java 内存模型(JMM) 抽象了线程和主内存之间的关系,就比如说线程之间的共享变量必须存储在主内存中。
主内存:所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)
本地内存:每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本。
Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
/**
* native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
*/
public final native Class<?> getClass()
/**
* native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
*/
public native int hashCode()
/**
* 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
*/
public boolean equals(Object obj)
/**
* native 方法,用于创建并返回当前对象的一份拷贝。
*/
protected native Object clone() throws CloneNotSupportedException
/**
* 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
*/
public String toString()
/**
* native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
*/
public final native void notify()
/**
* native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
*/
public final native void notifyAll()
/**
* native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
*/
public final native void wait(long timeout) throws InterruptedException
/**
* 多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。。
*/
public final void wait(long timeout, int nanos) throws InterruptedException
/**
* 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
*/
public final void wait() throws InterruptedException
/**
* 实例被垃圾回收器回收的时候触发的操作
*/
protected void finalize() throws Throwable { }
finalize 缺点
无法保证资源释放:FinalizerThread 是守护线程,代码很有可能没来得及执行完,线程就结束了
无法判断是否发生错误:执行 finalize 方法时,会吞掉任意异常(Throwable)
内存释放不及时:重写了 finalize 方法的对象在第一次被 gc 时,并不能及时释放它占用的内存,因为要等着 FinalizerThread 调用完 finalize,把它从 unfinalized 队列移除后,第二次 gc 时才能真正释放内存