JDK源码那些事儿之万物之源Object

从这篇文章开始进行AQS相关的学习,若是你还不明白什么是AQS,能够先去了解下,因为涉及的源码众多,笔者会一步一步进行深刻说明整理,在学习AQS前,有不少基础知识是须要咱们先去了解的,好比本文所说的Objectjava

前言

JDK版本号:1.8.0_171

我相信不少开发者都没有彻底去了解过Object类,只是使用的时候知道而已,其实其中仍是有不少能够学习的知识算法

Java中的父类也称为超类,而Java是面向对象的语言,面向对象的基础就是类。Java中全部类都有一个共同的祖先Object类,Object类是惟一没有父类的类,Object类引用变量能够存储任何类对象的引用。那么做为万物之源的Object类不知道你们有没有去看一看其中的源码呢?设计模式

为何先说Object类呢?由于其中有部分方法涉及到了多线程等待通知机制,也就是线程间通讯协做的一种实现方式,是咱们深刻AQS前应该先了解的知识,方便后期对比,加深理解数据结构

Object

接下来咱们一块儿看一看Object的源码实现多线程

registerNatives

以前的源码分析中已经说起到了这个方法的做用,这里就简单再说明下,其主要做用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。函数的执行是在静态代码块中执行的,在类首次进行加载的时候执行app

private static native void registerNatives();
    static {
        registerNatives();
    }

getClass

返回运行时对象的Class类型对象,也就是运行时实际的Class类型对象,一样是native方法,反射也是使用其来实现的框架

public final native Class<?> getClass();

hashCode/equals

hashCode返回对象的哈希码,是一个整数,这个整数是对象根据特定算法计算出来的一个散列值(hash值),而equals方法直接使用==进行比对,也就是比较对象的地址,默认的约定是相同的对象必须具备相同的哈希码。子类能够重写hashCode方法,可是重写后euqals方法一般也须要重写,这里就提出了那个经典的问题:为何hashCode和euqals重写其中一个方法,另外一个方法也建议重写?jvm

首先你应该知道的是这两个方法具备如下特性:函数

  • 两个对象经过equals()判断为true,则这两个对象的hashCode()返回值也必须相同
  • 两个对象的hashCode()返回值相同,equals()比较不必定须要为true,能够是不一样对象

为何要这么定义呢?其实咱们联想下HashMap就明白了,HashMap的底层数据结构就是哈希表,经过key的hashCode进行散列,可是这里咱们明白还须要解决hash冲突,这里就经过对象key的equals完成对比,咱们比较时是须要比较对象的属性值的,而不是对象地址,这也是咱们为何一般使用String对象来做为key,由于String已经对hashCode和equals进行了重写,若是咱们本身写了一个对象,没有进行重写,做为HashMap的key,你能够想一想会出现什么状况?源码分析

public native int hashCode();

    public boolean equals(Object obj) {
        return (this == obj);
    }

clone

clone方法是个本地方法,效率很高。当须要复制一个相同的对象时通常都经过clone来实现,而不是new一个新对象再把原对象的属性值等复制给新对象。Object类中定义的clone方法是protected的,必须在子类中重载这个方法才能使用。clone方法返回的是个Object对象,必须进行强制类型转换才能获得须要的类型。在设计模式中原型模式就是经过clone方法来完成的,有兴趣能够去查找资料进行了解

实现clone方法须要继承Cloneable接口,不继承会抛出CloneNotSupportedException异常

protected native Object clone() throws CloneNotSupportedException;

toString

能够看到默认的toString实现,这也就是咱们未重写时默认的输出,通常而言咱们建立对象时都会进行重写,便于日志输出

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

notify

咱们能够看到做者的注释:

唤醒正在此对象的monitor上等待的单个线程,若是有任何线程在该对象上等待,则选择其中一个唤醒。该选择是任意的,而且能够根据实现状况进行选择。线程经过调用wait方法在对象的monitor上等待。在当前线程放弃该对象上的锁以前,唤醒的线程将没法继续。唤醒的线程将以一般的方式与任何其余可能正在主动竞争以与此对象进行同步的线程竞争。此方法只能由做为该对象的monitor的全部者的线程调用。线程经过synchronized成为对象monitor的全部者,也就是获取对象的锁,同一时刻只有一个线程能够得到对象的锁

简单说明下,通知可能等待该对象的对象锁的其余线程。由JVM随机挑选一个处于wait状态的线程

  • 在调用notify()以前,线程必须得到该对象的对象级别锁
  • 执行完notify()方法后,不会立刻释放锁,要直到退出synchronized代码块,当前线程才会释放锁
  • notify()一次只随机通知一个线程进行唤醒
public final native void notify();

notifyAll

唤醒正在此对象的monitor上等待的全部线程,在当前线程放弃对该对象的锁定以前,唤醒的线程将没法继续。唤醒的线程将以一般的方式与可能正在竞争在此对象上进行同步的任何其余线程竞争,此方法只能由拥有该对象的monitor的全部者的线程调用

和notify功能差很少,只不过是使全部正在等待线程池中等待同一共享资源的所有线程从等待状态退出,进入可运行状态,让它们竞争对象的锁,只有得到锁的线程才能进入就绪状态

public final native void notifyAll();

wait

使当前线程等待,直到另外一个线程在对象上调用notify方法或notifyAll方法唤醒等待线程,能够等待指定的时间,在等待过程当中能够被中断,这里抛出InterruptedException异常

public final native void wait(long timeout) throws InterruptedException;

注意,在调用此方法前,当前线程必须先拥有对象的锁才能进行,注释上也说明了其使用方法:

synchronized (obj) {
       while (<condition does not hold>)
           obj.wait(timeout);
       ... // Perform action appropriate to condition
   }

finalize

当垃圾回收肯定再也不有对该对象的引用时,由垃圾回收器在对象上调用。子类覆盖finalize方法以处置系统资源或执行其余清除。finalize的通常约定是,当Java虚拟机肯定再也不有任何手段可使还没有死亡的任何线程访问该对象时(除非因为执行操做而致使),调用finalize。简单点说就是在垃圾回收以前调用,可是不推荐使用,了解下就好

protected void finalize() throws Throwable { }

synchronized

Object对象中wait/notify/notifyAll就是线程间进行通讯协做的一种方式,也就是一般的等待唤醒机制,只是在使用时咱们须要先获取对应的对象锁才能进行调用,通常而言经过synchronized完成

那么,synchronized是如何实现的呢?咱们经过代码来看看,方法上添加了synchronized和对代码块添加synchronized是不一样的

public synchronized void synchronizedMethod(){
        System.out.println("synchronizedMethod");
    }

    public void synchronizedBlock(){
        synchronized(this){
            System.out.println("synchronizedBlock");
        }
    }

咱们反编译下看看,能够看到在方法上的flags上添加了ACC_SYNCHRONIZED标识,而代码块上是多了monitorentermonitorexit两个jvm指令集,那么你应该明白了synchronized是经过JVM底层来实现的,既然不是Java API层面上的实现,那么先了解就好,咱们的重点学习部分在于Java API层面上的同步框架实现——AQS

public synchronized void synchronizedMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String synchronizedMethod
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 14: 0
        line 15: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/example/demo/SynchronizedTest;

  public void synchronizedBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #5                  // String synchronizedBlock
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return
      Exception table:
         from    to  target type
             4    14    17   any
            17    20    17   any
      LineNumberTable:
        line 18: 0
        line 19: 4
        line 20: 12
        line 21: 22
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      23     0  this   Lcom/example/demo/SynchronizedTest;

总结

本文简单介绍了万物之源Object的源码和其中的方法,重点关注下wait/notify/notifyAll,其须要结合synchronized来完成线程间通讯协做,经过反编译文件理解JVM对其进行的处理,以后会继续进行AQS的相关学习整理

以上内容若有问题欢迎指出,笔者验证后将及时修正,谢谢

相关文章
相关标签/搜索