wait()、notify()、notifyAll()方法
Object类里面提供了这几个方法:
wait():让当前线程处于等待(阻塞状态),直到其他线程调用此对象的notify()或notifyAll()方法(进入就绪状态)。
notify():唤醒在此对象监视器上等待的单个线程。
notifyAll():唤醒在此对象监视器上等待的所有线程。
每个方法都有finnal关键字修饰。
为什么这些方法要定义在Object类里,而不定义在Thread类里呢?
因为这些方法的调用必须通过锁对象调用,而锁对象可以是任意对象。所以定义在Object类里。
一个生产者—消费者问题
同一资源(共享数据):一个学生对象;设置学生对象的数据(生产者);获取学生对象的数据(消费者)。
考虑正常情况:
生产者:如果没有数据,则生产数据,生产完后通知消费者来消费数据;如果有,则自己等待;
消费者:如果有数据,则使用数据;如果没有,则自己等待,同时唤醒生产者生产数据。
###生产者-消费者问题之——采用等待唤醒机制解决
public class Student {
String name;
int age;
boolean flag;//默认初始化为flase 表示没有数据(姓名年龄)
}
//SetThread.java
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) { //锁:同一个对象:s
//已有数据了,则等待
if (s.flag) {
try {
s.wait(); //必须通过锁对象调用wait()方法 注意!!线程等待后,就立即释放锁。将来醒过来的时候是从这里醒过来的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有数据,就生产数据
if (x % 2 == 0) {
s.name = "hhh";
s.age = 22;
} else {
s.name = "ttt";
s.age = 33;
}
x++;
//生产完了,有数据了,通知一下消费者。它在等着呢
s.flag = true;
s.notify();//唤醒此监视器上在等待的线程 注意!!唤醒并不代表你可以立马执行,你必须还得抢cpu的执行权。
//做完活了,休息1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//GetThread.java
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {//锁:s
if (!s.flag) {
try {
s.wait();//注意!!线程等待后,就立即释放锁。将来醒过来的时候是从这里醒过来的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//消费完数据后,通知生产者生产
s.flag = false;
s.notify();//唤醒此监视器上在等待的线程
}
}
}
}
//测试类
public class StudentDemo {
public static void main(String[] args){
//创建资源对象
Student s= new Student();
//创建两个自定义的Runnable类的对象
SetThread mrs= new SetThread(s);//通过构造方法,把同一个资源对象传给其他类 SetThread类定义中当然要提供一个构造方法了
GetThread mrg= new GetThread(s);
//创建线程
//设置数据线程(生产者)
Thread setthread= new Thread(mrs);
//获取数据线程(消费者)
Thread getthread= new Thread(mrg);
//启动线程
//两个线程开始抢
setthread.start();
getthread.start();
}
}
分析:
假如setthread线程先抢到cpu的执行权,看SetThread类。此时还没有数据(s.flag=false),就生产数据(s(“hhh”,22)),假如在此时getthread线程抢到了cpu,看GetThread类。flag为false,getthread线程就wait()等待,注意它等待后,就立即释放锁。将来醒过来的时候是从这里醒过来的。 setthread执行了flag=true,notify()唤醒了在等待的getthread线程。它醒过来后如果抢到cpu的执行权就接着执行,下一步 System.out.println(s.name + “—” + s.age),即获取数据。
使用sychronized方法优化上述代码
资源类:
public class Student {
private String name;
private int age;
boolean flag;//默认初值为false,表示没有数据 标记有没有数据
public synchronized void set(String name, int age){
//已有数据,则等待
if(flag){
try {
this.wait();//同步方法的锁对象是:this 必须用锁对象调用wait、notify这些方法 Note!! 线程等待后,会立即释放锁。将来醒过来的时候是从这里醒过来的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有数据,则生产
this.name= name;
this.age= age;
flag=true;//修改标记 标记为已有数据
this.notify();//我已生产好数据了,唤醒在等待的线程
}
public synchronized void get(){
//没有数据,则等待
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有数据,则消费,即获取数据
System.out.println(name+"--"+age);
flag=false;//修改标记
this.notify();//唤醒在等待的线程 Note!!注意唤醒了之后,它未必执行。得看它能不能抢到cpu
}
}
自定义的两个Runnable类实现Runnable接口:SetThread和GetThread 重写run方法
public class SetThread implements Runnable {
private Student s;
private int x=0;
//提供一个这样的构造方法 初始化s
public SetThread(Student s){
this.s=s;
}
@Override
public void run() {
while(true){
if(x%2==0) {
s.set("hhh", 22);
}else{
s.set("ttt", 33);
}
x++;
}
}
}
public class GetThread implements Runnable {
private Student s;
//提供一个这样的构造方法 初始化s
public GetThread(Student s){
this.s=s;
}
@Override
public void run() {
while(true){
s.get();
}
}
}
测试类:
public class StudentDemo {
public static void main(String[] args){
//资源对象(线程们都在操作的共享数据)
Student s= new Student();
//创建两个自定义的Runnable类GetThread和SetThread类的对象
GetThread gmr= new GetThread(s);//构造方法
SetThread smr= new SetThread(s);
//创建线程
Thread setthread= new Thread(smr);
Thread getthread= new Thread(gmr);
setthread.start();
getthread.start();
}
}
运行结果:
hhh–22
ttt–33
hhh–22
ttt–33
hhh–22
ttt–33
hhh–22
ttt–33
hhh–22
ttt–33
hhh–22
ttt–33
hhh–22
ttt–33
…