------昨天理解的生产者和消费者模式,今天写一下我对这个模式的想法,然后我利用这个模式写了一个 读取文件的小程序。
------介绍:生产者和消费者模式,它不是一种设计模式,而是一种解决由多线程引发的同步问题的办法,也称为有限缓冲问题
------有一块固定容量的缓存区,有多个线程同时访问或操作这个块缓存区,而此时,一个线程称为生产者,生成数据到这块缓存,另一个线程消费者从这个块缓存里读取数据,这个过程就可能引发一系列线程安全问题。
------而解决问题的关键就是,使消费者和生产者同时只能有一个线程访问这块缓存,也就是说它们之间是互斥的。
------方法:当生产者生成数据的时候,消费者休眠,当缓存区满时,生产者停止生产,陷入休眠,而此时消费者就从休眠状态醒来,从缓存里取出数据
------实现此方法的一个思路就是使用信号灯法,设置一个标识,当标识为真的时候,消费者消费,生产者等待。当标识为假的时候,生产者生产,消费者等待.
------我贴一下我理解这个模式之后写小程序,这个程序的缓存区其实就是一个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();
}
}
如上,这就是我理解的生产者和消费者模式,希望大家不要误解,以为缓存区就是一个容器之类的东西,其实拓展开来,我的理解是,这个缓存区是一个为了降低两个线程之间的耦合,而与之交互的东西。
----好了,希望大家能从我的实例之中能理解多线程的一些东西,谢谢大家。。