wait()、notify()和notifyAll()

本文详细介绍了Java中Object类的wait()、notify()和notifyAll()等方法。wait()使线程等待并释放锁,notify()唤醒单个线程,notifyAll()唤醒所有线程。通过多个示例展示了这些方法的使用及效果,还解释了为何这些方法定义在Object类而非Thread类中。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在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类中的原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值