Java多线程之基于Synchronized的等待唤醒-03
文章目录
一、wait()、notify()、notifyAll()概述
在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() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
好了,接下来就开始讲解wait()、notify()、notifyAll()。
二、基于synchronized
接下来就讲解这三者的作用、使用、原理。
1、作用与原理
synchronized关键字用于保护共享数据,阻止其他线程对共享数据的存取,但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出synchronized数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。
- wait()方法使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池中。
- 当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。
- notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
2、使用
它们都是Object麾下的三名士兵。
为什么说是Object下的三名士兵呢?是因为三者都是Object类的方法,正是因为如此:
-
它们必须是由同步监视器(也就是syncrynized包围的那个对象)来调用, 用于协调多个线程对共享数据的存取。如果不是由同步对象来调用,则会抛出 java.lang.IllegalMonitorStateException异常。
就像:
synchronized (waitAndNotifyThread) {
try {
System.out.println(Thread.currentThread().getName()+" start t1");
waitAndNotifyThread.start();
// 主线程等待t1通过notify()唤醒。
System.out.println(Thread.currentThread().getN`ame()+" wait()");
//这里会抛出异常
Thread.currentThread().wait(0);
//如上代码的调用必须改成waitAndNotifyThread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- 如果不使用synchronized关键字对共享资源进行同步,就不应该使用wait()、notify()、notifyAll()。
下面用一个经典的demo来讲解这几个方法的使用:
//创建一个账户
public class Account {
//余额
private int rare;
public int getRare() {
return rare;
}
public void setRare(int rare) {
this.rare = rare;
}
/**
* 存款
*/
public void deposit(int money) {
rare += money;
}
/**
* 取款
*/
public void drawMoney(int money) {
rare -= money;
}
}
//创建测试
public class WaitAndNotifyTest {
static class Deposit implements Runnable {
private Account account;
public Deposit(Account account) {
this.account = account;
}
@Override
public void run() {
synchronized (account) {
//尝试存款5次
for (int i = 0; i < 5; i++) {
account.deposit(100);
System.err.println("第 " + i + " 次存款");
//唤醒其它线程
account.notify();
}
}
}
}
static class DrawMoney implements Runnable {
private Account account;
public DrawMoney(Account account) {
this.account = account;
}
@Override
public void run() {
//尝试取款10次,每次100
synchronized (account) {
for (int i = 0; i < 10; i++) {
if (account.getRare() >= 100) {
account.drawMoney(100);
System.err.println("第 " + i + " 次取款成功!");
} else {
try {
//如果余额不足,则放弃账户锁,当前线程进入等待。
System.err.println("第 " + i + " 次取款,余额不足,进入等待");
account.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) {
//创建一个账户
Account account = new Account();
//取款线程
Runnable drawMoneyThread = new DrawMoney(account);
//存款线程
Runnable depositThread = new Deposit(account);
new Thread(drawMoneyThread).start();
new Thread(depositThread).start();
}
}
以上的程序运行结果:
第 0 次取款,余额不足,进入等待
第 0 次存款
第 1 次存款
第 2 次存款
第 3 次存款
第 4 次存款
第 1 次取款成功!
第 2 次取款成功!
第 3 次取款成功!
第 4 次取款成功!
第 5 次取款成功!
第 6 次取款,余额不足,进入等待
结果分析:
- 首先创建一个共享的账户,Account,初始化余额为0
- 创建取款线程、存款线程
- 取款10次,每次都是100;存款5次,每次都是100
- 先运行取款线程,后运行存款线程。当取款余额不足,让当前线程等待,同时释放账户的锁;直到存款线程获取账户锁并且进行存款之后,取款线程重新获取账户锁执行取款。
注意:
- 调用这三个方法,让当前线程等待,中的“当前线程”,是对共享对象执行操作的线程。
三、总结
- wait、notify、notifyAll是Object麾下的操作方法,并非归属于Thread类
- wait、notify、notifyAll是基于synchronized的共享对象
- 如果不是由共享对象调用该三个方法,会抛出java.lang.IllegalMonitorStateException异常
- wait是将当前线程放入等待线程池中,而notify是从等待线程池中随机唤醒一个,notifyAll是从等待线程中唤醒所有线程,但是具体的对象锁的占用权交由各个线程自由竞争。
- wait方法调用后,线程状态由RUNNING变为WAITING;而notify方法调用之后,就会从等待队列中迁移线程到同步队列中,此时线程状态变为Blocked,则由对象头的moniter.exit之后,出队去获取对象锁。