day20Java-Thread-多线程中生产者和消费者

博客
Java-(高级)

多线程生产者和消费者

前面已经写过电影票程序,不是特别的符合实际情况。

多线程生产者和消费者:
就是不同种类的线程对同一个资源的操作。

自己对Java等待唤醒机制总结:
之前买电影票的线程只有一个卖票线程,创建多个线程对象去执行它。出现了线程安全问题可以使用同步来解决。

但是现在是不同种类的线程对同一个资源的操作。也可以使用同步来解决线程安全问题,线程安全问题解决了,但是打印的结果不是很符合实际情况,如果让不同种类的线程更符合实际情况,可以使用Java的等待唤醒机制。

线程通信问题:
		不同种类的线程间针对同一个资源的操作。

在这里插入图片描述

多线程生产者消费者代码版本1

代码演示
生产者消费者测试

public class StudnetThreadDemo {
    public static void main(String[] args) {
        //创建一个生产者消费者共享的对象
        Student s = new Student();
        
        //创建生产者线程对象
        SetThread st = new SetThread(s);
        //创建消费者线程对象
        GetThread gt = new GetThread(s);

        //启动线程
        st.start();
        gt.start();
    }
}

生产者

public class SetThread extends Thread {
    private Student s;

    public SetThread(Student s) {
        this.s = s;
    }
    @Override
    public void run() {
        int count = 0;
        while (true) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count % 2 == 0) {
                //Student s = new Student();
                s.name = "格雷福斯";
                s.age = 60;
            } else {
                s.name = "至高之拳";
                s.age = 30;
            }
            count++;
        }
    }
}

消费者

public class GetThread extends Thread {
    private Student s;

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

    @Override
    public void run() {
        //这里不能这样创建对象,那样就是各使用各的。
        //Student s = new Student();
        while (true) {
            /*try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
            System.out.println(s.name + "-" + s.age);
        }
    }
}

实体类

public class Student {
    public String name;
    public int age;
}

结果:我只列出来了有问题的

斯-60
格雷福斯-3
至高之拳-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
多线程生产者消费者代码版本2-同步解决问题
分析:
		资源类:Student	
		设置学生数据:SetThread(生产者)
		获取学生数据:GetThread(消费者)
		测试类:StudnetThreadDemo 

问题1:按照思路写代码,发现数据每次都是:null---0
原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
如何实现呢?
		在外界把这个数据创建出来,通过构造方法传递给其他的类。

问题2:为了效果更好一些,我加入的循环和判断,给出不同的值,但是出现了新的问题。
	A:相同的数据出现了多次
			原因:CPU一点时间片执行权,足够线程执行很多次了。
	B:姓名和年龄不匹配
			原因:因为多线程运行的随机性导致的

线程安全问题?
			A:是否是多线程环境?		是
			B:是否有共享数据? 	是 	Student
			C:是否有多条语句操作共享数据? 是	

解决方案:
 		加锁。

注意:
  			A:不同种类的线程都要加锁。
  				如果只有生产者一种类型线程加锁,还是会出现上面一样的问题。
  			B:不同种类的线程加的锁必须是同一把。
  				如果不同类型线程锁不同,各自锁各自的,也会出现上面一样的问题,那毫无意义。
加锁后:
	实现生产者在生产的时候,消费者不能消费不能消费(生产者那边已经把锁锁住了),消费者在消费的时候,生产者那边不能生产(消费者那边已经把锁锁住了)
	加锁后也就是把年龄和姓名不匹配的问题解决了,但是没有解决同一个数据出现多次。
	数据安全了。

代码演示
生产者消费者测试

public class StudnetThreadDemo {
    public static void main(String[] args) {
        //创建一个生产者消费者共享的对象
        Student s = new Student();

        //创建生产者线程对象
        SetThread st = new SetThread(s);
        //创建消费者线程对象
        GetThread gt = new GetThread(s);

        //启动线程
        st.start();
        gt.start();
    }
}

生产者

public class SetThread extends Thread {
    private Student s;

    public SetThread(Student s) {
        this.s = s;
    }
	int count = 0;
    @Override
    public void run() {
       
        while (true) {
            synchronized (s) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (count % 2 == 0) {
                    //Student s = new Student();
                    s.name = "格雷福斯"; //不加同步刚执行到这里,就被其他线程抢到执行权了,上面一次内存中的值可能是name=至高之
                    //拳,age=30,如果这时候输出值也就name="格雷福斯",age=30。 					 		    
                    s.age = 60;
                } else {
                    s.name = "至高之拳";//不加同步刚执行到这里,就被其他线程抢到执行权了,上一次内存中的值可能是name=格雷福斯,age=60
                    //如果这时候输出值也就是name=至高之拳,age=60;
                    s.age = 30;
                }
                count++;
            }
        }
    }
}

消费者

public class GetThread extends Thread {
    private Student s;

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

    @Override
    public void run() {
        //这里不能这样创建对象,那样就是各使用各的。
        //Student s = new Student();
        while (true) {
            synchronized (s) {
            /*try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
                System.out.println(s.name + "-" + s.age);
            }
        }
    }
}

实体类

public class Student {
    public String name;
    public int age;
}

结果:

格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
至高之拳-30
至高之拳-30
至高之拳-30
至高之拳-30
至高之拳-30
至高之拳-30
多线程生产者消费者代码版本3-等待唤醒机制解决问题
上面其实已经解决数据安全问题了,但是呢?数据输出相同的数据都是打印一大片。觉得不是很好看,想依次打印出来。
同步能解决线程安全问题。但是解决不了那种很理性的输出数据。

如何实现呢?
为了满足这样需求Java就提供了等待唤醒机制解决。
等待和唤醒:
		Object中提供了3个方法:	
					wait():等待。
					notify:唤醒单个线程。
					notify:唤醒所有线程。

为什么这些方法不定义在Thread类中呢?
 			这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
  			所以,这些方法必须定义在Object类中。

等待唤醒机制思路图解
在这里插入图片描述

代码演示
生产者消费者测试

public class StudnetThreadDemo {
    public static void main(String[] args) {
        //创建一个生产者消费者共享的对象
        Student s = new Student();

        //创建生产者线程对象
        SetThread st = new SetThread(s);
        //创建消费者线程对象
        GetThread gt = new GetThread(s);

        //启动线程
        st.start();
        gt.start();
    }
}

生产者

public class SetThread extends Thread {
    private Student s;

    public SetThread(Student s) {
        this.s = s;
    }
	int count = 0;
    @Override
    @SuppressWarnings("all")
    //生产者
    public void run() {   
        while (true) {
            synchronized (s) {
                //如果有数据就等待,没有数据就创建
                if(s.flag){
                    try {
                        s.wait(); //满足判断条件st等待,立即释放锁。st等待的情况下,只能gt抢到执行权。
                        //注意:醒来是从这里醒来的。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (count % 2 == 0) {
                    //Student s = new Student();
                    s.name = "格雷福斯";
                    s.age = 60;
                } else {
                    s.name = "至高之拳";
                    s.age = 30;
                }
                count++;
                //到这里说明数据已经生产好了,修改标记
                s.flag =true;
                //唤醒线程
                s.notify(); //唤醒线程,唤醒线程后不会立马执行。在抢CPU执行权。

                //st抢到或者gt抢到
                //情况1:如果是st抢到cpu执行权,刚才已经生产好数据了,那就要等待了。

                //情况2:gt抢到cpu执行权那就从消费者的wait()中醒来,继续向下执行。
            }
        }
    }
}

消费者

public class GetThread extends Thread {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }
    //消费者
    @Override
    public void run() {
        //这里不能这样创建对象,那样就是各使用各的。
        //Student s = new Student();
        while (true) {
            synchronized (s) {
                //如果有数据,就消费,没有就等待。
                if(!s.flag){
                    try {
                        s.wait();//满足判断条件gt等待,并立即释放锁。gt等待的情况下,只能st抢到执行权。
                        //注意:醒来是从这里醒来的。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name + "-" + s.age);
                //到这里说明已经消费完,修改标记
                s.flag = false;
                //唤醒线程
                s.notify();//唤醒线程,唤醒线程后不会立马执行。在抢CPU执行权。

                //st抢到或者gt抢到
                //情况1:如果gt抢到了cpu的执行权,数据已经消费完了,那就要等待了。

                //情况2:st抢到了cpu执行权那就从生产者wait()中醒来,继续向下执行。
            }
        }
    }
}

实体类

public class Student {
    public String name;
    public int age;
    public boolean flag; //定义一个标记,是否有学生,默认是false.没有数据
}

结果:

格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30

线程状态转换图
在这里插入图片描述

多线程生产者消费者代码版本4-代码优化

代码演示
生产者消费者测试

public class StudentDemo {
    public static void main(String[] args) {
        //创建学生对象
        Student s =  new Student();
        //创建线程对象
        SetThread st = new SetThread(s);
        GetThread gt=  new GetThread(s);

        //启动线程
        st.start();
        gt.start();
    }
}

生产者

public class SetThread extends Thread {
    private Student s;

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

    int count = 0;

    @Override
    public void run() {
        while (true) {
            if (count % 2 == 0) {
                s.setNameAge("格雷福斯", 60);
            } else {
                s.setNameAge("至高之拳", 30);
            }
            count++;
        }
    }
}

消费者

public class GetThread extends Thread {
    private Student s;

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

    @Override
    public void run() {
        while (true) {
            s.getNameAge();
        }
    }
}

实体类

public class Student {
    //实际开发中成员变量肯定是不能被外界访问的
    private String name;
    private int age;
    private boolean flag;

    public synchronized void setNameAge(String name, int age) {
        //如果数据存在就等待
        if (this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name;
        this.age = age;
        //修改标记
        this.flag = true;
        //唤醒线程
        this.notify();
    }

    public synchronized void getNameAge() {
        //如果没有数据就等待
        if (!this.flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(this.name + "-" + this.age);
        //修改标记
        this.flag = false;
        //唤醒线程
        this.notify();
    }
}

结果:

格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值