java并发编程-线程通讯机制(为什么notify(), wait()等函数定义在Object中,而不是Thread中)
1.先看一下线程的生命周期转换图(学java的此图必背)
本篇文章的主要目的不是分析线程的各种状态之间的转换,而主要是研究一下线程之间的通讯机制,以及Object的wait方法和notify方法。所以下面就以这两个方法为主展开讨论。
首先说明者两个方法的作用:
- wait():使调用该方法的线程释放该同步锁资源,然后从运行状态退出,进入等待队列,直到被再次唤醒。
- notify():随机唤醒等待队列中等待同一同步锁资源的一个线程,并使该线程退出等待队列,进入锁池状态,在获取同步锁对象后进入可运行状态,也就是notify()方法仅通知一个线程。
- notifyAll():使所有正在等待队列中等待同一同步锁资源的全部线程退出等待队列,进入锁池状态开始竞争同步锁资源,竞争到同步锁资源的,进入可运行状态。然后在获取到时间片后开始执行代码。
从上述对方法的说明中可以看出,这几个方法的执行都和同步锁有关,没错,这也是下面要说明的一点,那就是wait方法和notify/notifyAll方法的执行必须在同步代码块中。因为在介绍wait方法的时候说过,在调用同步资源的wait方法的时候会使调用该方法的线程释放同步锁,既然要释放同步锁,那调用方法之前就要先获取同步锁。notify方法也是一样,另外notify()是依据什么唤醒等待线程的呢?答案就是一直提到的同步锁对象。
举个简单的例子
public class A8 {
public static void main(String[] args) {
A8 a=new A8();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (a) {
a.test();
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
synchronized (a) {
a.test2();
}
}
}).start();
}
public void test() {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---");
}
public void test2() {
this.notify();
System.out.println("***");
}
}
当然也可以写成这个样子
public class A8 {
public static void main(String[] args) {
A8 a=new A8();
new Thread(new Runnable() {
@Override
public void run() {
a.test();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
a.test2();
}
}).start();
}
public synchronized void test() {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---");
}
public synchronized void test2() {
this.notify();
System.out.println("***");
}
}
要实现线程之间的通讯,多个线程锁住的同步资源必须是同一个对象。如果是下方这样的可不行哟!
public static void main(String[] args) {
A8 a=new A8();
A8 a2=new A8();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (a) {
a.test();
}
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
synchronized (a2) {
a2.test2();
}
}
}).start();
}
如果在没有获取同步资源时调用wait/notify时则会报错,如下例子:
public class A8 {
public static void main(String[] args) {
A8 a=new A8();
new Thread(new Runnable() {
@Override
public void run() {
a.test();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
@Override
public void run() {
a.test2();
}
}).start();
}
public void test() {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("---");
}
public void test2() {
this.notify();
System.out.println("***");
}
}
报错:
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Unknown Source)
at a.A8.test(A8.java:26)
at a.A8$1.run(A8.java:9)
at java.lang.Thread.run(Unknown Source)
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at a.A8.test2(A8.java:33)
at a.A8$2.run(A8.java:20)
at java.lang.Thread.run(Unknown Source)
2.再来说说 为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。