Java基础之Object解析(二)

1. 导读

接上一篇的分享, 我们一起看一下Object类中剩余的6个方法:
1.1 toString();
1.2 notify();
1.3 notifyAll();
1.4 wait();
1.5 finalize();
1.6 registerNatives();

2. toString方法

toString方法是我们比较常用的方法, 在Object中的默认实现返回一个 类名+'@'+hasCode的16进制拼接的字符串;

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

注意到toString方法是没有被final修饰的, 证明他可以被子类重写;

划重点:
.1 我们可以根据自身需求重写toString方法, 默认实现是返回类名+'@'+hashCode的16进制拼接而成的字符串;
.2 我们调用System.out.println(obj), 默认调用的就是obj的toString方法;

3. notify, notifyAll 和 wait方法

把这三个方法放一起是因为这三个方法是配套的, 用来实现JAVA多线程的协作; 既然是多线程相关的方法, 为什么会在Object这个类中呢? 这个是因为调用这三个方法前提都是需要在synchronized修饰的同步代码块中, 而synchronized锁的实现是基于对象(Object)监视器的;
那么这三个方法之间是如何协作的? 每个方法又具体干了什么呢?
3.1 wait: 在同步代码块中调用该方法时, 当前线程立即释放锁并等待, 直到有其他线程调用notify/notifyAll或超时等待时, 才会去再次竞争锁, 成功后继续执行下面的逻辑;

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

wait方法被final, native修饰, 证明他是不可被重写的原生方法; 该方法在等待的时候, 有其他线程打断了他的等待, 那么他会抛出InterruptedException并退出等待;
Object类中还有wait(), wait(long timeout, int nanos)这两种wait的实现, 但其实都是调用的wait(long timeout);

3.2 notify: 线程A在同步代码块中调用该方法时, 会随机地唤醒一个等待在该对象锁上的线程B, 注意这时候唤醒的线程B还没有持有锁, 必须要等到线程A释放锁后才能持有该把锁;
当线程在A对象的同步代码块中执行B对象的notify时, 会抛出IllegalMonitorStateException;如果没有这个限制, 我们想想会发生什么情况;
拿最常用的生产者消费者举例:
1. 消费者消费(notify生产者);
2. 当货物不存在时等待生产者生产(wait);
3. 生产者生产货物(notify消费者);
4. 当货物没被消费时等待(wait);
我们期望的是 1-->2-->3-->4-->1-->2...这样的顺序;
但是如果消费者和生产者持有的是两把不同的对象锁, 那么当消费者notify时, 因为生产者等待在另一把锁上, 导致无法唤醒生产者, 那么就会导致:1-->2-->4,生产者和消费者会同时阻塞;
所以为了消除这种竞态条件, 在A对象的同步代码块中, 只能调用A对象的notify方法, 否则就会抛出IllegalMonitorStateException;

3.3 notifyAll: 线程A在同步代码块中调用该方法时, 会唤醒所有等待在该锁上的线程, 同样的, 这些唤醒的线程只有在线程A释放锁以后, 才能再次竞争该把锁, 竞争到锁的线程继续执行, 其他的线程继续等待;
如果调用的线程不是该把锁的持有者, 那么也会抛出IllegalMonitorStateException;

    public final native void notify();
    public final native void notifyAll();

nofity 和 notifyAll都是不可重写的原生方法, 虽然这两个方法没有显式的抛出IllegalMonitorStateException这个异常, 但是当竞态条件产生时, IllegalMonitorStateException这个异常自然就出现了;

划重点:
.1 执行notify | notifyAll时, 唤醒的线程并不会立即持有锁, 故而会形成假唤醒的情况, 那么在写wait方法的时, 推荐使用以下方式:

     synchronized(lock) {
        while(!condition) {
            lock.wait();
        }
        //doSomething;
     }

当条件不满足时, 该线程还需继续等待;

.2 执行wait | notify | notifyAll的对象, 必须与synchronized锁住的对象是同一个, 否则会形成竞态条件导致IllegalMonitorStateException异常的产生;
.3 java多线程这块内容较多, 这期只是简略的介绍下这三个方法, 后面会有一系列的文章专门分享多线程相关的内容;

4. finalize方法

     protected void finalize() throws Throwable { }

java的内存管理依赖于JVM实现的GC(Garbage Collection)机制来实现内存的回收, GC相关的内容后面再展开; JVM在进行GC时, 如果这个对象需要被回收, 会先判断该方法是否有被重写, 若未重写, 则直接回收该对象内存空间;
反之则判断该对象的finalize是否被执行过, 如果没有执行过, 会先放入一个队列中, 由低优先级的线程去执行该对象的finalize方法, 执行完毕后再判断该对象是否需要回收;
如果该对象已经执行过一遍finalize方法了, 直接回收对象的内存空间;


image

上图就是对GC执行回收对象finaliz方法时对象状态变化的过程;

划重点:
.1 重写了finalize方法后, 在对象的整个生命周期中GC只会执行一次finalize方法;

5. registerNatives方法

最后来看一下registerNatives方法, 可能看过源码的同学都知道这个方法是Object类中的第一个方法, 我把他放到最后将的原因是和他的功能相关;

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

首先应该关注到的是static代码块, 静态代码块是在类加载时就会执行的, 这个代码块中只是调用了registerNatives方法;
再看到registerNatives方法:
5.1 static: 这是个静态方法, 因为静态只能调用静态, 也就是只有静态方法才能在静态代码块中直接调用;
5.2 native: 原生方法, 他是由C实现的;
5.3 看到方法名, 我们可以猜到他是注册Object类中的原生方法的, 实现java中声明的native方法与C实现函数的绑定;

static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
    (*env)->RegisterNatives(env, cls,
                            methods, sizeof(methods)/sizeof(methods[0]));
}

这是OpenJDK6对Object中native方法的绑定, Java_java_lang_Object_registerNatives这样函数可直接调用java中native函数, 通过上面的代码可以清晰的看到registerNatives实现了method中native方法的绑定;
同时也可以看到methods中是没有getClass这个方法, 自然可以猜到他也是采用规定函数名称直接调用的方式实现绑定的(Java_java_lang_Object_getClass);
JNI这里就不做展开, 感兴趣的可以阅读下
Java? Native Interface: Programmer's Guide and Specification;

至此, Object类中的所有方法的解析已经告一段落了, 如有错误之处, 欢迎指正;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354