多线程之生产者和消费者模式(信号灯法)

4 篇文章 0 订阅
3 篇文章 0 订阅

------昨天理解的生产者和消费者模式,今天写一下我对这个模式的想法,然后我利用这个模式写了一个 读取文件的小程序。

------介绍:生产者和消费者模式,它不是一种设计模式,而是一种解决由多线程引发的同步问题的办法,也称为有限缓冲问题

------有一块固定容量的缓存区,有多个线程同时访问或操作这个块缓存区,而此时,一个线程称为生产者,生成数据到这块缓存,另一个线程消费者从这个块缓存里读取数据,这个过程就可能引发一系列线程安全问题。

------而解决问题的关键就是,使消费者和生产者同时只能有一个线程访问这块缓存,也就是说它们之间是互斥的。

------方法:当生产者生成数据的时候,消费者休眠,当缓存区满时,生产者停止生产,陷入休眠,而此时消费者就从休眠状态醒来,从缓存里取出数据

------实现此方法的一个思路就是使用信号灯法,设置一个标识,当标识为真的时候,消费者消费,生产者等待。当标识为假的时候,生产者生产,消费者等待.

------我贴一下我理解这个模式之后写小程序,这个程序的缓存区其实就是一个byte数组,然后它的生产者就是读取流,读取流生产的行为,就是读取数据到缓存区,直到缓存区满,再通知消费者消费。而消费者是输出流,输出流的消费行为就是从缓存区取出数据到并输出到控制台。而每次消费者消费都是将缓存区里的数据全部拿出来,所以每次读取的时候都需要将缓存区置为空。

------可以从上面我对这个程序的介绍里看出,生产者和消费者其实是没有任何关系的,它们只负责和缓存区交互,所以降低了两个模块的耦合性。

代码: Buffer 类(缓存区)

public class Buffer {
 
      // 读取数据的缓存区
         private volatile byte[] bytes = new byte[1024];
  
     //信号灯法 判断该线程生产还是消费
     private  boolean flag = false;
     
     //  读取的字节数,因为这里的生产行为是读取文件的数据,所以每次读取的	时候,都从上次读取的位置开始
       private int pos = 0;
       
        /**信号灯发的的核心*/
   		 //如果flag为真,那么消费者消费,生产者等待
        //如果flag为假,那么生产者生产,消费者等待
        //线程的等待用wait,唤醒用notify

    //生产行为
    public synchronized int produce(File file){
            if(flag){
                try {
                    //flag为真,所以生产者等待
                    this.wait();
                    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        // flag为假,所以生产者生产,也就是读取文件的数据到缓存区
            try {
            		//每次生产间隔1000ms
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //读取数据到缓存区,并接收每次的取的字节数
            pos = read(file);

            //生产完毕后,生产者再次等待
         flag = true;
         //并且唤醒消费者消费
         this.notify();
         
         //返回每次读取的字节数,如果读到文件末尾,返回-1,这也是
         //作为生产者停止的条件。
        return pos;
    }

        //消费的行为
        public synchronized void consume(){
            if(!flag){
                try {
                    //flag为假,所以消费者等待
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //flag为真,所以消费者消费
            try{
            //每次消费休眠1000ms
                Thread.sleep(1000);
            }catch(InterruptedException inte){
                inte.printStackTrace();
            }
            //消费,也就是从缓存里取出数据,输入到控制台
           prints();

            //使消费者再次等待
            flag = false;
            //唤醒生产者
            this.notify();
        }

 
      // 读取方法
     private int read(File file){
     //利用RandomAccessFile类可以定位到上次读取文件的位置
          try(RandomAccessFile rf = new RandomAccessFile(file,"r")){
          	//设置流到文件指针所在的位置,每次流就会从此处开始读取
            	  rf.seek(pos);
            
                //读取数据,并且返回每次的取的字节数
                pos = rf.read(bytes);
              
                //如果指针为 -1 说明读取到了文件的末尾,那么直接返回。
                if(-1 == pos)
                    return -1;
                    //每次读取都重置文件指针的位置
                pos = (int)rf.getFilePointer();

          }catch(FileNotFoundException fnfe){
              fnfe.printStackTrace();
          }catch(IOException ioe){
              ioe.printStackTrace();
          }
          //返回文件指针
         return pos;
     }

   
     //输出方法,每次读取玩之后,将缓存区置为空,这样,
     //可以不断的读取和写入数据
    
     private void prints(){
         try {
         		//这里的输出方法,大家可以根据自己的编码设置,因为
         		//RandomAccessFile无法指定读取时的编码,
         		//所以我只能在输出的时候指定
             System.out.println(new String(bytes,"UTF-8"));
         
         } catch (UnsupportedEncodingException e) {
             e.printStackTrace();
         }
         //重置缓存区
                bytes = null;
                bytes = new byte[1024];
     }
}

生产者类

//生产者生成数据到缓存区里,不用和消费者联系,所以只需要缓存区
public class Producer extends Thread{
    private Buffer bff;
    public Producer(Buffer bff){this.bff = bff;}

    @Override
    public void run() {
        super.run();
        int judge = 0;
        //以每次读取返回的文件指针来作为结束条件,读到文件末尾,线程就结束
        while(-1 != judge){
                judge = bff.produce(new File("自己的文件名或路径"));
        }
    }
}

消费者类

    //因为消费者只从缓存区里消费数据,所以只需要缓存就可以消费,而不用和生产者直接联系
public class Consumer extends Thread{
    private Buffer bff;
    private Thread th;
    
    public Consumer(Buffer bff,Thread th){this.bff = bff;this.th = th;}
    //这里判断消费者线程退出的条件是,生产者线程还未死亡,可以理解为只要生产者未死亡
    //消费者就可以消费
    @Override
    public void run(){
        while(th.isAlive())
             bff.consume();
    }
}

如上,这就是我理解的生产者和消费者模式,希望大家不要误解,以为缓存区就是一个容器之类的东西,其实拓展开来,我的理解是,这个缓存区是一个为了降低两个线程之间的耦合,而与之交互的东西。

----好了,希望大家能从我的实例之中能理解多线程的一些东西,谢谢大家。。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值