多线程之等待唤醒机制(wait-notify)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013317445/article/details/82749707

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

猜你喜欢

转载自blog.csdn.net/u013317445/article/details/82749707
今日推荐