四、生产者和消费者

我们这里的生产者和消费者模型为:

    生产者Producer 生产某个对象(共享资源),放在缓冲池中,然后消费者从缓冲池中取出这个对象。也就是生产者生产一个,消费者取出一个。这样进行循环。

  第一步:我们先创建共享资源的类 Person,它有两个方法,一个生产对象,一个消费对象


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public class Person {

    private String name;

    private int age;

    

    /**

     * 生产数据

     * @param name

     * @param age

     */

    public void push(String name,int age){

        this.name = name;

        this.age = age;

    }

    /**

     * 取数据,消费数据

     * @return

     */

    public void pop(){

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

    }

}

  第二步:创建生产者线程,并在 run() 方法中生产50个对象


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public class Producer implements Runnable{

    //共享资源对象

    Person p = null;

    public Producer(Person p){

        this.p = p;

    }

    @Override

    public void run() {

        //生产对象

        for(int i = 0 ; i < 50 ; i++){

            //如果是偶数,那么生产对象 Tom--11;如果是奇数,则生产对象 Marry--21

            if(i%2==0){

                p.push("Tom"11);

            }else{

                p.push("Marry"21);

            }

        }

    }

}

  第三步:创建消费者线程,并在 run() 方法中消费50个对象


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public class Consumer implements Runnable{

    //共享资源对象

    Person p = null;

    public Consumer(Person p) {

        this.p = p;

    }

    

    @Override

    public void run() {

        for(int i = 0 ; i < 50 ; i++){

            //消费对象

            p.pop();

        }

    }

}

  由于我们的模型是生产一个,马上消费一个,那期望的结果便是 Tom---11,Marry--21,Tom---11,Mary---21......   连续这样交替出现50次

但是结果却是:


1

2

3

4

5

6

7

8

9

10

11

Marry---21

Marry---21

Marry---21

Marry---21

Marry---21

......

Marry---21

Marry---21

Marry---21

Marry---21

Marry---21

为了让结果产生的更加明显,我们在共享资源的 pop() 和 push() 方法中添加一段延时代码


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

/**

     * 生产数据

     * @param name

     * @param age

     */

    public void push(String name,int age){

        this.name = name;

        try {

            //这段延时代码的作用是可能只生产了 name,age为nul,消费者就拿去消费了

            Thread.sleep(10);

        catch (InterruptedException e) {

            e.printStackTrace();

        }

        this.age = age;

    }

    /**

     * 取数据,消费数据

     * @return

     */

    public void pop(){

        try {

            Thread.sleep(10);

        catch (InterruptedException e) {

            e.printStackTrace();

        }

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

    }  

  

这个时候,结果如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

Marry---11

Tom---21

Marry---11

Tom---21

Marry---11

Tom---21

Marry---11

Tom---21

......

Tom---11

Tom---21

Marry---11

Tom---21

Marry---11

Marry---21

  

结果分析:这时候我们发现结果全乱套了,Marry--21是固定的,Tom--11是固定的,但是上面的结果全部乱了,那这又是为什么呢?而且有很多重复的数据连续出现,那这又是为什么呢?

原因1:出现错乱数据,是因为先生产出Tom--11,但是消费者没有消费,然后生产者继续生产出name为Marry,但是age还没有生产,而消费者这个时候拿去消费了,那么便出现 Marry--11。同理也会出现 Tom--21

原因2:出现重复数据,是因为生产者生产一份数据了,消费者拿去消费了,但是第二次生产者生产数据了,但是消费者没有去消费;而第三次生产者继续生产数据,消费者才开始消费,这便会产生重复

解决办法1:生产者生产name和age必须要是一个整体一起完成,即同步。生产的中间不能让消费者来消费即可。便不会产生错乱的数据。如何同步可以参考:

        Java 多线程详解(三)------线程的同步:http://www.cnblogs.com/ysocean/p/6883729.html

     这里我们选择同步方法(在方法前面加上 synchronized)


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

public class Person {

    private String name;

    private int age;

    

    /**

     * 生产数据

     * @param name

     * @param age

     */

    public synchronized void push(String name,int age){

        this.name = name;

        try {

            //这段延时代码的作用是可能只生产了 name,age为nul,消费者就拿去消费了

            Thread.sleep(10);

        catch (InterruptedException e) {

            e.printStackTrace();

        }

        this.age = age;

    }

    /**

     * 取数据,消费数据

     * @return

     */

    public synchronized void pop(){

        try {

            Thread.sleep(10);

        catch (InterruptedException e) {

            e.printStackTrace();

        }

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

    }

}

  结果如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

Marry---21

Marry---21

Marry---21

Marry---21

Marry---21

Tom---11

Tom---11

......

Tom---11

Tom---11

Tom---11

Tom---11

Tom---11

问题:还是没有解决上面的问题2,出现重复的问题。期望的结果是 Tom---11,Marry--21,Tom---11,Mary---21......   连续这样交替出现50次。那如何解决呢?

解决办法:生产者生产一次数据了,就暂停生产者线程,等待消费者消费;消费者消费完了,消费者线程暂停,等待生产者生产数据,这样来进行。

这里我们介绍一个同步锁池的概念:

  同步锁池:同步锁必须选择多个线程共同的资源对象,而一个线程获得锁的时候,别的线程都在同步锁池等待获取锁;当那个线程释放同步锁了,其他线程便开始由CPU调度分配锁

关于让线程等待和唤醒线程的方法,如下:(这是 Object 类中的方法)

  

  

wait():执行该方法的线程对象,释放同步锁,JVM会把该线程放到等待池中,等待其他线程唤醒该线程

notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待(注意锁池和等待池的区别)

notifyAll():执行该方法的线程唤醒在等待池中等待的所有线程,把线程转到锁池中等待。

注意:上述方法只能被同步监听锁对象来调用,这也是为啥wait() 和 notify()方法都在 Object 对象中,因为同步监听锁可以是任意对象,只不过必须是需要同步线程的共同对象即可,否则别的对象调用会报错:        java.lang.IllegalMonitorStateException

假设 A 线程和 B 线程同时操作一个 X 对象,A,B 线程可以通过 X 对象的 wait() 和 notify() 方法来进行通信,流程如下:

①、当线程 A 执行 X 对象的同步方法时,A 线程持有 X 对象的 锁,B线程在 X 对象的锁池中等待

②、A线程在同步方法中执行 X.wait() 方法时,A线程释放 X 对象的锁,进入 X 对象的等待池中

③、在 X 对象的锁池中等待锁的 B 线程获得 X 对象的锁,执行 X 的另一个同步方法

④、B 线程在同步方法中执行 X.notify() 方法,JVM 把 A 线程从等待池中移动到 X 对象的锁池中,等待获取锁

⑤、B 线程执行完同步方法,释放锁,等待获取锁的 A 线程获得锁,继续执行同步方法

那么为了解决上面重复的问题,修改代码如下:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

public class Person {

    private String name;

    private int age;

    

    //表示共享资源对象是否为空,如果为 true,表示需要生产,如果为 false,则有数据了,不要生产

    private boolean isEmpty = true;

    /**

     * 生产数据

     * @param name

     * @param age

     */

    public synchronized void push(String name,int age){

        try {

            //不能用 if,因为可能有多个线程

            while(!isEmpty){//进入到while语句内,说明 isEmpty==false,那么表示有数据了,不能生产,必须要等待消费者消费

                this.wait();//导致当前线程等待,进入等待池中,只能被其他线程唤醒

            }

            

            //-------生产数据开始-------

            this.name = name;

            //延时代码

            Thread.sleep(10);

            this.age = age;

            //-------生产数据结束-------

            isEmpty = false;//设置 isEmpty 为 false,表示已经有数据了

            this.notifyAll();//生产完毕,唤醒所有消费者

        catch (Exception e) {

            e.printStackTrace();

        }

        

    }

    /**

     * 取数据,消费数据

     * @return

     */

    public synchronized void pop(){

        try {

            //不能用 if,因为可能有多个线程

            while(isEmpty){//进入 while 代码块,表示 isEmpty==true,表示为空,等待生产者生产数据,消费者要进入等待池中

                this.wait();//消费者线程等待

            }

            //-------消费开始-------

            Thread.sleep(10);

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

            //-------消费结束------

            isEmpty = true;//设置 isEmpty为true,表示需要生产者生产对象

            this.notifyAll();//消费完毕,唤醒所有生产者

        catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

}

  

结果:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

Tom---11

Marry---21

Tom---11

Marry---21

Tom---11

Marry---21

Tom---11

......

Marry---21

Tom---11

Marry---21

Tom---11

Marry---21

Tom---11

Marry---21  

那么这便是我们期待的结果,交替出现。

死锁:

①、多线程通信的时候,很容易造成死锁,死锁无法解决,只能避免

②、当 A 线程等待由 B 线程持有的锁,而 B 线程正在等待由 A 线程持有的锁时发生死锁现象(比如A拿着铅笔,B拿着圆珠笔,A说你先给我圆珠笔,我就把铅笔给你,而B说你先给我铅笔,我就把圆珠笔给你,这就造成了死锁,A和B永远不能进行交换)

③、JVM 既不检测也不避免这种现象,所以程序员必须保证不能出现这样的情况

Thread 类中容易造成死锁的方法(这两个方法都已经过时了,不建议使用):

suspend():使正在运行的线程放弃 CPU,暂停运行(不释放锁)

resume():使暂停的线程恢复运行

情景:A 线程获得对象锁,正在执行一个同步方法,如果 B线程调用 A 线程的 suspend() 方法,此时A 暂停运行,放弃 CPU 资源,但是不放弃同步锁,那么B也不能获得锁,A又暂停,那么便造成死锁。

解决死锁法则:当多个线程需要访问 共同的资源A,B,C时,必须保证每一个线程按照一定的顺序去访问,比如都先访问A,然后B,最后C。就像我们这里的生产者---消费者模型,制定了必须生产者先生产一个对象,然后消费者去消费,消费完毕,生产者才能在开始生产,然后消费者在消费。这样的顺序便不会造成死锁。

原文地址:https://www.cnblogs.com/zhoanghua/p/9292151.html

时间: 2024-10-29 04:23:06

四、生产者和消费者的相关文章

实验四生产者和消费者

实验四生产者和消费者 一.实验目的 1.掌握临界区的概念及临界区的设计原则: 2.掌握信号量的概念.PV操作的含义以及应用PV操作实现进程的同步与互斥: 3.分析进程争用资源的现象,学习解决进程互斥的方法. 二.实验内容和要求 分析进程的同步与互斥现象,编程实现经典的进程同步问题——生产者消费者问题的模拟 生产者--消费者问题表述: 有一环形缓冲池,包含n个缓冲区(0~n-1). 有两类进程:一组生产者进程和一组消费者进程,生产者进程向空的缓冲区中放产品,消费者进程从满的缓冲区中取走产品. 所有

实验四、生产者和消费者

一.    实验目的 1.掌握临界区的概念及临界区的设计原则: 2.掌握信号量的概念.PV操作的含义以及应用PV操作实现进程的同步与互斥: 3.分析进程争用资源的现象,学习解决进程互斥的方法. 二.    实验内容及要求 分析进程的同步与互斥现象,编程实现经典的进程同步问题——生产者消费者问题的模拟 生产者--消费者问题表述: 有一环形缓冲池,包含n个缓冲区(0~n-1). 有两类进程:一组生产者进程和一组消费者进程,生产者进程向空的缓冲区中放产品,消费者进程从满的缓冲区中取走产品,所有进程必须

C# 线程(四):生产者和消费者

From : http://kb.cnblogs.com/page/42530/ 前面说过,每个线程都有自己的资源,但是代码区是共享的,即每个线程都可以执行相同的函数.这可能带来的问题就是几个线程同时执行一个函数,导致数据的混乱,产生不可预料的结果,因此我们必须避免这种情况的发生. C#提供了一个关键字lock,它可以把一段代码定义为互斥段(critical section),互斥段在一个时刻内只允许一个线程进入执行,而其他线程必须等待.在C#中,关键字lock定义如下: lock(expres

JAVA-初步认识-第十四章-线程间通信-多生产者多消费者问题-JDK1.5解决办法

一. 在1.5版本中,将原先的形式进行了改变,但是功能并没有任何变化,那么这么做的原因是什么? 以前,我们一个锁上只有一组监视器,这组监视器既监视着生产者,又监视着消费者.这组监视器能将生产者和消费者全都wait,也能将生产者和消费者全都唤醒.或者notify也行,它也能将其中一条线程唤醒,而其中一条不能确定是谁,有可能是本方,也可能是对方. 现在我们的线程进行了分类,一组负责生产,一组负责消费.我们希望生产者能够唤醒消费者,消费者唤醒生产者.如果搞两个监视器,一组监视生产者,一组监视消费者,这

多线程——生产者和消费者(四)

通过前面三篇博客的介绍,基本上对Java的多线程有了一定的了解了,然后这篇博客根据生产者和消费者的模型来介绍Java多线程的一些其他知识. 我们这里的生产者和消费者模型为: 生产者Producer 生产某个对象(共享资源),放在缓冲池中,然后消费者从缓冲池中取出这个对象.也就是生产者生产一个,消费者取出一个.这样进行循环.   第一步:我们先创建共享资源的类 Person,它有两个方法,一个生产对象,一个消费对象 ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

生产者与消费者模式(线程的同步与互斥)

死锁产生的四个条件: 1.互斥使用(资源独占) 一个资源每次只能给一个进程使用 .2.不可强占(不可剥夺) 资源申请者不能强行的从资源占有者手中夺取资源,资源只能由占有者自愿释放 .3.请求和保持(部分分配,占有申请) 一个进程在申请新的资源的同时保持对原有资源的占有(只有这样才是动态申请,动态分配) .4.循环等待 存在一个进程等待队列 {P1 , P2 , - , Pn}, 其中P1等待P2占有的资源,P2等待P3占有的资源,-,Pn等待P1占有的资源,形成一个进程等待环路 生产者:生产数据

生产者、消费者模型

转载地址:http://blog.csdn.net/snow_5288/article/details/72794306 一.概念引入 日常生活中,每当我们缺少某些生活用品时,我们都会去超市进行购买,那么,你有没有想过,你是以什么身份去的超市呢?相信大部分人都会说自己是消费者,确实如此,那么既然我们是消费者,又是谁替我们生产各种各样的商品呢?当然是超市的各大供货商,自然而然地也就成了我们的生产者.如此一来,生产者有了,消费者也有了,那么将二者联系起来的超市又该作何理解呢?诚然,它本身是作为一座交

生产者与消费者问题解决:解决先打印出消费的情况

有bug 的代码(马士兵老师讲解的): 1 <span style="font-size:14px;">/**生产者消费者问题,涉及到几个类 2 * 第一,这个问题本身就是一个类,即主类 3 * 第二,既然是生产者.消费者,那么生产者类和消费者类就是必须的 4 * 第三,生产什么,消费什么,所以物品类是必须的,这里是馒头类 5 * 第四,既然是线程,那么就不是一对一的,也就是说不是生产一个消费一个,既然这样,多生产的往哪里放, 6 * 现实中就是筐了,在计算机中也就是数据结

7.2.6 - 并发多线程 生产者,消费者

一 生产者消费者模型介绍 为什么要使用生产者消费者模型 生产者指的是生产数据的任务,消费者指的是处理数据的任务,在并发编程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据.同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者.为了解决这个问题于是引入了生产者和消费者模式. 什么是生产者和消费者模式 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题.生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生