栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java 多线程 —— 生产者消费者问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java 多线程 —— 生产者消费者问题

生产者消费者模型,是通过一个容器来解决生产者和消费者的强耦合问题。
假设一个生活场景,我们去商店的货架上购买商品,如果这个商品有货,那么就拿走商品。而商店看到货架上的商品被拿走了,就及时补货,放上去新的商品。

这里将这一过程抽象为一个资源类,一个消费者线程,一个生产者线程

package test.MyThread.ProductDemo;

public class Student {
    public String name;
    public int age;
    public boolean flag;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

假设这个Student学生类是货架,name姓名和age年龄是货架上面的商品。而flag标志则用来说明货架是否为空。

消费者线程获取学生的姓名和年龄等同于消费者获取商品。生产者给学生对象赋新的姓名和年龄的值,等同于生产者生产商品

这里将消费者获取了name和age就将flag设置为false,即货架为空。而生产者给name和age设置新的值,就将flag设置为true,即货架已满

在编写生产者和消费者的线程的代码前,首先有几个问题要来解决:

  1. 我们要确保生产者和消费者,是针对同一个学生对象操作的。
    因为消费者消费的商品一定是生产者生产的,如果操作的对象不一样,那么消费者永远取不到商品。
    解决办法是:在主方法的外面构造学生对象,然后给生产者和消费者都加上一个有参构造方法,将学生对象作为参数传入生产者和消费者之中。而不是在线程内部新建一个学生对象

  2. 这是否涉及到线程安全问题,来看看线程安全问题的三个条件
    (1)是否存在多线程环境 (存在,生产者和消费者线程)
    (2)是否存在共享数据 (存在,Student对象)
    (3)是否有多条语句操作着共享数据 (是)
    解决方法:synchronized,同步代码块
    当生产者给对象赋值的时候,消费者无法操作对象取值,避免了生产者只给对象的name赋值,而消费者已经把name和age的值都取走的情况

  3. 线程抢到了CPU后,可以在很短的时间内运转多次。
    这样可能出现只有一个货架,生产者生产完商品后,还没有等消费者来取,就不断的生产新的商品来替代货架上的旧商品的情况。或者消费者已经将商品取走,还在对着空的货架不断取商品的情况。
    解决办法:加入等待唤醒机制
    等待唤醒机制通过Object类中的三个方法来实现(注意是Object,不是Thread):
    void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
    void notify() 唤醒正在等待对象监视器的单个线程。
    void notifyAll() 唤醒正在等待对象监视器的所有线程。

这三个方法的具体使用是:

  1. wait()
    在调用 wait()之前,线程必须要获得相应对象的对象监视器锁(这里是Student对象),所以只能在同步方法或同步块中调用 wait()方法。对象调用wait()方法后,当前线程会立即释放锁,然后进入休眠状态,直到被notify()唤醒或者被中断。同时线程的执行会在wait()语句处停止,直到再次获得锁,当前线程才能从wait()方法处成功返回,然后继续执行下面的代码。另外,被释放的锁会立刻被其他等待锁的线程抢夺,抢到锁的线程开始执行同步代码块。
  2. notify()
    notify()同样需要获得锁,并且只能在同步方法或同步代码块中调用。当前线程调用notify()后,会唤醒之前wait()后陷入休眠的线程,使其从等待队列进入同步队列,获取当前线程的锁,并且从之前wait()语句处继续执行(这里要求对象锁的对象要一致,才会去唤醒)。当等待唤醒的线程较多时,会根据机制随机挑选一个线程唤醒。当前线程调用notify()方法后不会立刻释放锁,而是继续执行,直到执行结束退出同步方法或同步代码块时,才会释放锁。
  3. notifyAll()
    notifyAll()与notify()的工作方式大致相同,不同的是等待线程较多时,notify()会随机挑选一个线程通知,而notifyAll()会将所有具有相同对象锁的线程全部唤醒,让这些线程争抢锁。

这里注意wait()与notify(),notifyAll()联系的桥梁是相同的对象,即synchronized(对象){ },不同的线程之间,括号里面的对象相同

生产者线程

package test.MyThread.ProductDemo;

public class SetThread implements Runnable{
    Student s ;
    int x = 0;

    public SetThread(Student s){
        this.s = s;
    }

    @Override
    public void run() {
        while(true){
            synchronized(s){
                if(s.flag){
                    //说明已经给学生对象赋值,应该等待消费者来获取
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                //这里是两类商品,当x%2==0的时候上架商品水果,否则上架商品面包
                //这里放在学生类里面想当于一个叫水果的学生,一个叫面包的学生
                    if(x%2==0){
                        s.setName("水果");
                        s.setAge(18);
                    }else{
                        s.setName("面包");
                        s.setAge(15);
                    }
                    x++;
                    //赋值完毕,将状态转为true,并通知消费者来取值
                    s.flag = true;
                    s.notify();
                }

            }
        }
    }
}

消费者线程

package test.MyThread.ProductDemo;

public class GetThread implements Runnable{
    Student s ;
    public GetThread(Student s){
        this.s = s;
    }
    @Override
    public void run() {
        while(true){
            synchronized(s){
            //s.flag为false时,!s,flag为true,运行if语句
                if(!s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(s.name + "---" + s.age);

                s.notify();
                s.flag = false;
            }
        }
    }
}

s.flag=true说明有商品,s.flag=false说明没有商品
这里没有商品的时候应该用锁对象调用wait()方法,线程释放锁对象,并且代码的执行停留在这一步
锁对象被释放后,会被生产者抢夺,而消费者不会再抢夺锁对象
然后生产者生产商品完毕后,用notify()方法唤醒消费者,并且将锁对象给消费者
调用notify()方法后不会立刻释放锁对象,而是等生产者将同步代码块全部运行完之后才会释放给消费者
这样生产者会继续从头运行,检测是否有商品,如果商品还没有被消费者消费,那么就进入等待状态
最后消费者接着执行代码,消费商品,消费完毕后用notify()方法唤醒生产者生产商品

package test.MyThread.ProductDemo;

public class StudentDemo {
    public static void main(String[] args) {
        Student s = new Student();

        SetThread s1 = new SetThread(s);
        GetThread g1 = new GetThread(s);

        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(g1);

        t1.start();
        t2.start();
    }
}

运行一下,结果为

生产者不断的给学生类的name和age属性赋予新的值,然后消费者再不断的获取这些值并打印输出。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/349040.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号