关于为什么线程通信的方法wait()、notify()、notifyAll()被定义在Object类里面?而sleep为什么定义在Thread类里面?

导语

在面试中经常会被问到多线程的知识,而在这个知识范围内,我们也会被问到关于wait、notify等这些方法。一般基础点的都会问使用的方法,当然这只是初级的问题,那么稍微深点的问题,可能就像我们标题这样:为什么线程通信的方法wait()、notify()、notifyAll()被定义在Object类里面?而sleep定义在Thread类里面?

其实这样的问题更多的是体现在程序实际上面,意思就是说我们要从设计层面理解这样的问题,对于这样的问题,我们自己在日常的学习中,也需要自己多多的去思考,毕竟Java作为这样一个存活了几十年的语言,他其中的一些设计自然是有他的道理的,我们就不多说什么了,快点进入主题。

关于wait()、notify()的使用

可能又人会说了,你刚才不是说使用是最基础的嘛?不过只有当我们学会了使用后,然后才能从使用中去发现一些问题。因为使用就为了体现一些原理的,否则也只属于意淫的状态,说到这里我们先看段代码(关于标题中的notifyAll,本文章就不多做解释,因为它和notify类似,且都属于Object类),如下:

public class WaitDemo {
    public static Object object = new Object();
    
    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }

    static class Thread1 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println("线程" + Thread.currentThread().getName() + "开始执行!");
                try {
//                    System.out.println("线程" + Thread.currentThread().getName() + "调用了wait()");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁!");
            }
        }
    }

    static class Thread2 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                object.notify();
                System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
            }
        }
    }
}

wait()、notify()、notifyAll()被定义在Object类里面?

这个问题其实有点奇怪,我们就好比说wait方法不这么用,那怎么用。我们 先从代码中来看,首先在代码里面,我们创建了一个Object对象,然后我们synchronized锁定的便是object,我在之前的文章Java并发编程之synchronized底层实现原理中提到过,当进入synchronized时当前线程便会持有monitor,如果不能获取到这个monitor的话,wait方法是会抛异常的。当然从这个角度解析的话,是非常抽象的。

其实我们从代码中还可以看出这样的一个问题,就是我们在两个线程中所用的是一个对象,在Thread1中我们使用object对象来调用wait方法,然后在Thread2中使用object去调用notify方法来唤醒线程调用wait方法的线程,当前代码的执行顺序的结果是:

线程Thread-0开始执行
线程Thread-1调用了notify()
线程Thread-0获取到了锁

这从另一个角度也证明了,wait是用来释放锁的。从上面的整个代码逻辑中,可以看出wait、notify、notifyAll被设计在Object类中的原因是,JAVA提供的锁是对象级的而不是线程级的,每个对象都有个锁,而线程是可以获得这个对象的。因此线程需要等待某些锁,那么只要调用对象中的wait()方法便可以了。而wait()方法如果定义在Thread类中的话,那么线程正在等待的是哪个锁就不明确了。这也就是说wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中是因为锁是属于对象的原因。

sleep为什么定义在Thread类里面?

对于sleep为什么被定义在Thread中,我们只要从sleep方法的作用来看就知道了,sleep的作用是:让线程在预期的时间内执行,其他时候不要来占用CPU资源。从上面的话术中,便可以理解为sleep是属于线程级别的,它是为了让线程在限定的时间后去执行。而且sleep方法是不会去释放锁的(本段代码是为了展示线程sleep时,不释放synchronized的monitor,等sleep时间到了之后,正常结束之后才释放锁),我们可以写段代码来证明下:

public class SleepDontReleaseMonitor implements Runnable {

    public static void main(String[] args) {
        SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
        new Thread(sleepDontReleaseMonitor).start();
        new Thread(sleepDontReleaseMonitor).start();
    }

    @Override
    public void run() {
        sync();
    }

    private synchronized void sync() {
        System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor!");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块!");
    }

}

这段代码的执行结果是:

线程Thread-0获取到了monitor!
线程Thread-0退出了同步代码块!
线程Thread-1获取到了monitor!
线程Thread-1退出了同步代码块!

从执行结果可以看出只有当Thread-1退出同步代码块后,Thread-0才可以退出代码块,而Thread-0 又是先进入代码块的,由此可见sleep方法是不会去释放锁的,这便不满足在锁内使用的意义了。(修改后:从执行结果可以看出只有当Thread-0退出同步代码块后,Thread-1才可以继续获取锁,而Thread-0 又是先进入代码块的,由此可见sleep方法是不会去释放锁的monitor,这便满足在锁内使用的意义了。)

总结

在上面我们分别从几个方面对标题的疑问进行了简答的解析,就是从使用中的使用方式和最后的结果来进行分析,wait()、notify()、notifyAll()被定义在Object类里面和sleep为什么定义在Thread类里面的原因。

  • 10
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值