在Object.java中,定义了wait()、notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程——notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
Object类中关于等待/唤醒的API详细信息如下:
notify() ——唤醒在此对象监视器上等待的单个线程
notifyAll() ——唤醒在此对象监视器上等待的所有线程
wait() ——让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的notify()或notifyAll()方法,当前线程被唤醒进入就绪状态
wait(long timeout)——让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的notify()或notifyAll()方法,或者超过指定的时间量,当前线程被唤醒进入就绪状态
wait(long timeout, int nanos) ——让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量,当前线程被唤醒进入就绪状态
1、wait()和notify()示例:
public class Thread1 extends Thread {
public Thread1(String name) {
super(name);
}
@Override
public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " call notify");
notify();
}
}
//测试
public class MainTest {
public static void main(String args[]) {
Thread1 t1 = new Thread1("thread1");
synchronized (t1) {// 和Thread1中的run方法公用一个锁对象
try {
System.out.println(Thread.currentThread().getName() + " start " + t1.getName());
t1.start();
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName() + " call " + t1.getName() + " wait");
t1.wait();
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
main start thread1
main call thread1 wait//10秒钟之后该行及后续行才会依次打印
thread1 call notify
main continue
①示例中Thread1类的run()中同步代码块的锁是this——当前对象,和测试类main()中同步代码块的锁是同一个,因此主线程和t1线程存在互斥关系,不能同时执行
②main()是程序的入口,因此主线程在进入同步代码块时先获取到锁对象,打印出:main start thread1,紧接着调用t1.start(),开启新的线程,此时run()开始在新线程中执行,执行到同步代码块时由于锁被主线程占用run()进入阻塞状态
③主线程在睡眠10秒钟后打印出:main call thread1 wait,紧接着调用t1.wait(),将持有t1这个锁对象的当前线程即此时的主线程
置为等待状态,并将锁对象释放
④主线程释放锁对象后,子线程获取到锁对象,接着执行同步代码块中的代码,打印出:thread1 call notify,紧接着调用notify(),其实调用的是this.notify()即t1.notify(),又将持有锁对象t1的线程(即此处的主线程)唤醒,主线程继续执行,打印出:main continue
注意:如果在Thread1类中的run()的同步代码块中没有调用notify(),则在打印出:thread1 call notify之后,主线程也不会继续执行,因为之前在主线程中调用了wait()使主线程阻塞,只有在线程被唤醒或者时间超过等待时间之后才会继续执行,这说明了一个问题:wait()和notify()都是作用于持有调用这两个方法的对象作为锁的线程,比如:obj.wait()会使当前将obj作为锁的运行中状态的线程变为阻塞状态,obj.notify()则会使当前将obj作为锁的处于阻塞状态的线程变为待执行状态,也就是说调用wait()和notify()的是锁对象,而这两个方法却作用于持有该对象为锁的线程,并不影响锁对象本身。
2、wait(long timeout)和notify()
wait(long timeout)会让持有该对象同步锁的当前线程处于“等待(阻塞)状态”,直到在其他线程中调用此对象的notify()或notifyAll(),或者超过指定的时间量,当前线程会被唤醒进入“就绪状态”,示例:
public class Thread1 extends Thread {
public Thread1(String name) {
super(name);
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " print");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 测试
public class MainTest {
public static void main(String args[]) {
Thread1 t1 = new Thread1("thread1");
synchronized (t1) {
try {
System.out.println(Thread.currentThread().getName() + " start " + t1.getName());
t1.start();
Thread.sleep(3000);
System.out.println(">>>>>>" + Thread.currentThread().getName() + " call " + t1.getName() + " wait");
t1.wait(5000);
System.out.println(">>>>>>" + Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
main start thread1
thread1 print
thread1 print
>>>>>>main call thread1 wait
thread1 print
thread1 print
thread1 print
thread1 print
thread1 print
>>>>>>main continue
thread1 print
thread1 print
thread1 print
thread1 print
...
分析:
1、主线程中新建了一个子线程对象,在为调用该子线程对象的start()之前,只有主线程在运行
2、进入主线程的同步代码块,打印出:main start thread1之后,调用了t1.start(),此时子线程被启动,程序中有两个线程同时运行
3、紧接着主线程休眠了3秒钟,因此这3秒内看起来只有子线程在运行(其实主线程也在运行),打印出两行:thread1 print后主线程苏醒,打印出:>>>>>>main call thread1 wait
4、再接着主线程调用wait()阻塞了5秒钟,这五秒中只有子线程在运行,打印出五行:thread1 print后阻塞时间到,主线程从阻塞状态变为就绪状态,在获取到CPU资源之后执行并打印出:>>>>>>main continue,之后主线程执行完毕,进入死亡状态,子线程则在死循环中一致运行下去
这里稍微将程序改动一下,将Thread1中的run方法同步,那么结果就会不一样:
public class Thread1 extends Thread {
public Thread1(String name) {
super(name);
}
@Override
public void run() {
synchronized (this) {
while (true) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " print");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//测试
public class MainTest {
public static void main(String args[]) {
Thread1 t1 = new Thread1("thread1");
synchronized (t1) {
try {
System.out.println(Thread.currentThread().getName() + " start " + t1.getName());
t1.start();
Thread.sleep(3000);
System.out.println(">>>>>>" + Thread.currentThread().getName() + " call " + t1.getName() + " wait");
t1.wait(5000);
System.out.println(">>>>>>" + Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
main start thread1
>>>>>>main call thread1 wait//3秒钟后打印
thread1 print
thread1 print
thread1 print
thread1 print
thread1 print
...
分析:由于主线程和子线程使用的是同一个对象的同步锁,主线程在调用锁对象的wait()方法之后释放锁,此后,子线程获取到锁并进入死循环,主线程等待时间超时依然得不到执行,因为子线程没有释放锁,这里的结果也说明sleep()不释放锁而wait()释放锁
3、wait()和notifyAll()
notifyAll()的作用:唤醒在此对象监视器上等待的所有线程,示例:
public class NotifyAllThread {
private static Object obj = new Object();
public static void main(String args[]) {
ThreadInner t1 = new ThreadInner("t1");
ThreadInner t2 = new ThreadInner("t2");
ThreadInner t3 = new ThreadInner("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName() + " sleep 5s");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " notifyAll()");
obj.notifyAll();
}
}
static class ThreadInner extends Thread {
public ThreadInner(String name) {
super(name);
}
@Override
public void run() {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + " wait");
obj.wait();// wait后释放锁,t2和t3才能进入该代码块
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
结果:
main sleep 5s
t1 wait
t2 wait
t3 wait
main notifyAll()
t3 continue
t2 continue
t1 continue
分析:
1、主类中有一个静态属性obj和一个静态内部类ThreadInner,主类中main()中的同步代码块的锁和静态内部类run()中同步代码块的锁是同一个,都是obj
2、主线程中先创建了3个子线程并一一启动,此时会打印出:这四句话的顺序是不确定的,看哪个线程先获取CPU资源
main sleep 5s
t1 wait
t2 wait
t3 wait
说明:t1、t2和t3的同步代码块中的锁是同一个,都是obj,那为什么在t1还没执行完同步代码块就打印出了t2 wait、t3 wait呢?是因为在t1中调用obj.wait()时会释放锁,这才使得线程t2和t3得以进入同步代码块
3、主线程还没有进入同步代码块之前三个子线程都已处于阻塞状态(wait),主线程在休眠了5秒后进入同步代码块,并调用obj.notifyAll()唤醒所有持有obj为锁的线程,即t1、t2、t3,此时打印出:这四句的第一句一定优先打印,因为只有主线程的同步代码块执行完之后才会释放锁,子线程才会获得锁得以执行,后三句的顺序是不确定的
main notifyAll()
t3 continue
t2 continue
t1 continue
这里还要注意一下,主线程中的同步不能去掉,否则会报错:如下这样调用notifyAll()方法会报错
//synchronized (obj) {
System.out.println(Thread.currentThread().getName() + " notifyAll()");
obj.notifyAll();
//}
报错的原因是因为当前线程没有持有obj对象为锁,所以在某个线程中调用某个对象的notify()、notifyAll()、wait()时需要保证该线程持有该对象为锁
4、为什么notify(),wait()等方法定义在Object中,而不是Thread中
①因为任何一个对象都可以充当锁
②Object中的wait(),,notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作,等待线程可以被notify()或notifyAll()唤醒,那么这wait()、notify()和notifyAll()之间靠什么进行通信呢?只能靠调用这三个方法的对象本身,或者说该对象的同步锁
注:
负责唤醒等待线程的那个线程,我们称为“唤醒线程”。它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个)并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然等待线程被唤醒,但是它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”,必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。总之,notify(),,wait()依赖于“同步锁”,而“同步锁”是对象所持有,并且每个对象有且仅有一个!这就是为什么notify()、 wait()等函数定义在Object类中,而不是Thread类中的原因。