JavaSE基础知识-多线程-wait()和notifyAll()方法
·
一、案例需求
创建两个线程(t1 和 t2),交替输出数字 1-10:t1 输出 1,t2 输出 2,t1 输出 3,t2 输出 4…… 直至 10。
核心思路
线程通信的关键是 “协作”:一个线程执行完后,通知另一个线程执行,自己进入等待状态。具体依赖两个核心方法(定义在Object类中):
wait():释放持有的锁,进入等待状态,直到被notify唤醒(唤醒后需先重新竞争锁);notifyAll():唤醒所有当前对象的等待线程。
通过这两个方法,可实现线程的 “交替执行”:t1 输出后唤醒 t2,自己等待;t2 输出后唤醒 t1,自己等待,循环往复。
二、代码实现与解析
完整代码
// 主类:启动两个线程
public class Main {
public static void main(String[] args) {
MyClass sharedObj = new MyClass(); // 共享对象(锁对象)
Thread t1 = new Thread(new Processor(sharedObj), "t1");
Thread t2 = new Thread(new Processor(sharedObj), "t2");
t1.start();
t2.start();
}
}
// 共享类:包含交替输出的方法
public class MyClass {
// t1执行的方法:输出奇数(1,3,5,7,9)
public synchronized void print1() {
for (int i = 0; i < 5; i++) { // 输出5次(1-10中的奇数)
int num = 2 * i + 1; // 计算当前输出的奇数
System.out.println("print1->" + num);
this.notifyAll(); // 唤醒等待当前对象锁的线程(t2)
try {
this.wait(); // 释放锁,自己进入等待状态,等待t2唤醒。唤醒后需先重新竞争锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll(); // 循环结束后,唤醒可能等待的t2(避免死等)
}
// t2执行的方法:输出偶数(2,4,6,8,10)
public synchronized void print2() {
for (int i = 0; i < 5; i++) { // 输出5次(1-10中的偶数)
int num = 2 * (i + 1); // 计算当前输出的偶数
System.out.println("print2->" + num);
this.notifyAll(); // 唤醒等待当前对象锁的线程(t1)
try {
this.wait(); // 释放锁,自己进入等待状态,等待t1唤醒。唤醒后需先重新竞争锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll(); // 循环结束后,唤醒可能等待的t1(避免死等)
}
}
// 线程任务类:根据线程名调用不同方法
public class Processor implements Runnable {
private MyClass sharedObj;
public Processor(MyClass obj) {
this.sharedObj = obj;
}
@Override
public void run() {
// t1调用print1,t2调用print2
if ("t1".equals(Thread.currentThread().getName())) {
sharedObj.print1();
} else {
sharedObj.print2();
}
}
}
代码解析
1. 共享对象与锁机制
MyClass的print1和print2方法都用synchronized修饰,意味着它们的锁对象是MyClass的实例(sharedObj)。- 两个线程(t1 和 t2)共享同一个
sharedObj,因此会竞争同一把锁,保证同一时间只有一个线程执行print1或print2。
2. 交替执行的核心逻辑
以 t1 和 t2 的第一次执行为例:
- t1 先获取锁:执行
print1,输出 “print1->1”; - t1 唤醒 t2:调用
this.notifyAll(),唤醒等待sharedObj锁的 t2(此时 t2 从 “等待池” 进入 “锁池”); - t1 释放锁并等待:调用
this.wait(),t1 释放锁并进入 “等待池”,等待被唤醒; - t2 获取锁:t2 从锁池竞争到锁,执行
print2,输出 “print2->2”; - t2 唤醒 t1:调用
this.notifyAll(),唤醒等待的 t1(t1重新尝试获取锁); - t2 释放锁并等待:调用
this.wait(),t2 释放锁并进入等待池(t1获取锁); - 循环往复:t1 和 t2 交替获取锁,输出 3、4…… 直至 10。
三、wait()与notifyAll()的使用规范
wait()和notifyAll()是线程通信的核心,但使用时有严格规范,否则会导致IllegalMonitorStateException(非法监视器状态异常)。
1. 必须在同步块 / 同步方法中调用
wait()和notifyAll()的调用者必须是 “当前持有锁的对象”,且必须在synchronized修饰的代码块或方法中使用。
错误示例(不在同步块中调用):
public void wrongMethod() {
// 错误:未持有锁时调用wait()
this.wait(); // 抛IllegalMonitorStateException
}
正确示例(在同步方法中调用):
public synchronized void rightMethod() {
this.wait(); // 正确:当前线程持有this锁
}
2. wait()会释放锁,sleep()不会
wait():释放持有的锁,进入等待状态,直到被notify唤醒重新竞争锁;sleep(long):不释放锁,线程进入阻塞状态,时间到后自动唤醒。
这是两者的核心区别,线程通信必须用wait()(需释放锁让其他线程执行)。
3. notify()与notifyAll()的区别
notify():随机唤醒一个等待该锁的线程,让它竞争锁;notifyAll():唤醒所有等待该锁的线程,让它们竞争锁。
实际开发中优先用notifyAll(),避免 “信号丢失”(如只唤醒一个线程但该线程不需要执行,导致其他线程永久等待)。
四、总结:线程通信的核心要点
- 锁是基础:线程通信必须基于同一把锁(共享对象),通过
synchronized保证锁的唯一性; wait()释放锁等待:让当前线程释放锁并进入等待状态,给其他线程执行机会;notifyAll()唤醒协作:唤醒所有等待同一把锁的线程,让它们竞争锁继续执行;- 避免死锁:循环结束后必须唤醒等待线程,用
notifyAll()而非notify()减少信号丢失风险。
掌握线程通信机制可以更好理解如生产者 - 消费者模型、交替打印等并发程序。
更多推荐


所有评论(0)