闲来无事,写了段生产者/消费者的代码。写的过程中遇到了一些问题,有些地方着实费了一番周折,特写此文以备后忘。
1.代码层次
实现中定义了四个类分别是comm、ReadPort、WritePort、SerialBuffer。
class comm:<main-class >
public class Comm ... {
public static void main(String[] args) ...{
SerialBuffer sb=new SerialBuffer();
ReadPort rp0=new ReadPort(sb);
ReadPort rp=new ReadPort(sb);
WritePort wp=new WritePort(sb);
WritePort wp1=new WritePort(sb);
rp0.start();
wp.start();
wp1.start();
rp.start( );
}
}
class ReadPort:
function: 向临界区加入数据
class ReadPort extends Thread ... {
private SerialBuffer serialBuffer;
private boolean isStop=false;
private byte[] b=new byte[]...{0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x20,0x21,0x22};
public ReadPort(SerialBuffer sb)...{
serialBuffer=sb;
}
public void run()...{
while(!isStop)...{
try...{
sleep(2000);
}catch(InterruptedException e)...{}
for(int i=1;i<50;i++)...{
serialBuffer.addInBuffer(b);
}
isStop=true;
}
}
}
class WritePort:
function:从临界区取出数据
class WritePort extends Thread ... {
SerialPort serialPort;
OutputStream os;
private boolean isStop=false;
private SerialBuffer serialBuffer;
public WritePort(SerialBuffer sb)...{
serialBuffer=sb;
}
public void run()...{
while(!isStop)...{
try...{
sleep(2000);
}catch(InterruptedException e)...{}
serialBuffer.delFromBuffer();
}
}
}
class SerialBuffer:
function:临界区存储数据
class SerialBuffer ... {
//默认size 和递增size
public static int SERIALBUFFER_SIZE=24;
public static int SERIALBUFFER_INCREMENT=512;
private ByteBuffer b;
private boolean debug=true;
public SerialBuffer()...{
b=ByteBuffer.allocateDirect(SERIALBUFFER_SIZE);
}
public synchronized void addInBuffer(byte[] _in)...{
System.out.println(" ====== "+Thread.currentThread().getName()+" 第"+(curNum++)+"次写入 ======");
while(b.position()==b.capacity()||(b.position()+_in.length>b.capacity()))...{
try...{
System.out.println("当前缓存区已满,不能再加入数据,暂时休眠.");
this.wait();
}catch(InterruptedException e)...{
System.out.println("异常:当前缓存区已满,暂时休眠.");
}
}
System.out.println("加入"+_in.length+"个字节");
b.put(_in);
notify();
}
public synchronized byte[] delFromBuffer()...{
System.out.print(" "+Thread.currentThread().getName()+" 开始取数据");
//flip();
//while(b.limit()==0){
for(b.flip();b.limit()==0;b.flip())...{//这里 返回到下文
try...{
System.out.println("当前无数据可取,暂时休眠.");
b.clear();
this.wait();
}catch(InterruptedException e)...{
System.out.println("异常:当前无数据可取,暂时休眠.");
}
}
System.out.println(Thread.currentThread().getName()+" 可以取数据。。。");
byte[] rebyte=new byte[b.limit()];
System.out.println("取出"+rebyte.length+"个字节");
b.get(rebyte);
b.clear();
notify();
return rebyte;
}
}
2.临界区处理
临界区使用了java nio中的byteBuffer作为数据缓存,第一次使用byteBuffer遇到了些小问题,在这里原本用flip();+while(conditions)
在一个生产者、一个消费者时没有问题,但是调整到多个生产者和多个消费者时,会抛出BufferUnderflowException异常
3.运行结果
设置1个生产者/2个消费者时的运行结果:
Thread-1 开始取数据当前无数据可取,暂时休眠.
Thread-2 开始取数据当前无数据可取,暂时休眠.
====== Thread-0 第1次写入 ======
加入12个字节
Thread-1 可以取数据。。。
取出12个字节
当前无数据可取,暂时休眠.
====== Thread-0 第2次写入 ======
加入12个字节
====== Thread-0 第3次写入 ======
加入12个字节
====== Thread-0 第4次写入 ======
当前缓存区已满,不能再加入数据,暂时休眠.
Thread-2 可以取数据。。。
取出24个字节
加入12个字节
====== Thread-0 第5次写入 ======
加入12个字节
====== Thread-0 第6次写入 ======
当前缓存区已满,不能再加入数据,暂时休眠.
Thread-1 开始取数据Thread-1 可以取数据。。。
取出24个字节
加入12个字节
====== Thread-0 第7次写入 ======
加入12个字节
====== Thread-0 第8次写入 ======
当前缓存区已满,不能再加入数据,暂时休眠.
Thread-2 开始取数据Thread-2 可以取数据。。。
取出24个字节
加入12个字节
====== Thread-0 第9次写入 ======
加入12个字节
====== Thread-0 第10次写入 ======
当前缓存区已满,不能再加入数据,暂时休眠.
Thread-1 开始取数据Thread-1 可以取数据。。。
取出24个字节
加入12个字节
====== Thread-0 第11次写入 ======
加入12个字节
====== Thread-0 第12次写入 ======
当前缓存区已满,不能再加入数据,暂时休眠.
Thread-2 开始取数据Thread-2 可以取数据。。。
取出24个字节
加入12个字节
====== Thread-0 第13次写入 ======
加入12个字节
====== Thread-0 第14次写入 ======
当前缓存区已满,不能再加入数据,暂时休眠.
Thread-1 开始取数据Thread-1 可以取数据。。。
取出24个字节
Thread-2 开始取数据当前无数据可取,暂时休眠.
加入12个字节
====== Thread-0 第15次写入 ======
加入12个字节
====== Thread-0 第16次写入 ======
当前缓存区已满,不能再加入数据,暂时休眠.
Thread-2 可以取数据。。。
取出24个字节
加入12个字节
====== Thread-0 第17次写入 ======
加入12个字节
====== Thread-0 第18次写入 ======
当前缓存区已满,不能再加入数据,暂时休眠.
Thread-1 开始取数据Thread-1 可以取数据。。。
取出24个字节
加入12个字节
====== Thread-0 第19次写入 ======
加入12个字节
Thread-2 开始取数据Thread-2 可以取数据。。。
取出24个字节
Thread-1 开始取数据当前无数据可取,暂时休眠.
Thread-2 开始取数据当前无数据可取,暂时休眠.
小结
对于多个生产者和多个消费者访问临界区时产生异常的问题,我分析产生的原因如下:1.首先要明确byteBuffer在读数据前需要调flip()方法,从而对position和limit以及remaining等值进行设置。2.是当取数据线程经过一次wait,再次由runnable转换为run状态时,由于当前运行环境可能已经发生了变化(即不同于此线程发生wait时的环境),所以需要重新flip()。但是flip()方法此时却访问不到,(在while(conditions)之外!)所以没有经过flip()直接get()就可能会抛出异常。