导语
在面试中经常会被问到多线程的知识,而在这个知识范围内,我们也会被问到关于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方法是不会去释放锁的,我们可以写段代码来证明下:
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 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-1获取到了monitor!
线程Thread-1退出了同步代码块!
线程Thread-0退出了同步代码块!
从执行结果可以看出只有当Thread-1退出同步代码块后,Thread-0才可以退出代码块,而Thread-0 又是先进入代码块的,由此可见sleep方法是不会去释放锁的,这便不满足在锁内使用的意义了。
总结
在上面我们分别从几个方面对标题的疑问进行了简答的解析,就是从使用中的使用方式和最后的结果来进行分析,wait()、notify()、notifyAll()被定义在Object类里面和sleep为什么定义在Thread类里面的原因。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/19404.html