Java双缓冲队列实现

前言

        在某一模块中,需要将网络接收到的数据存入Oracle中。这是一个典型的生产者消费者场景,可以使用消息队列隔离生产者和消费者。由于接收的数据频度很高,而Oracle的插入速度较慢,为不影响接收端吞吐量,选择了双缓冲队列作为消息队列。双缓冲队列的原理是一般情况下生产者使用写队列,消费者使用读队列,两个线程不需要做同步保护。当读队列消费完的时候,将读写队列交换,生成者使用空的读队列,消费者使用写队列。双缓冲队列只需在这时对读写队列进行同步保护即可,可大幅提高性能。

        在使用Java开始编码时发现,Java的多线程同步和C++的差距有点大,怎么会没有条件变量、信号量、互斥锁?百度了几个实例后基本了解了Java的同步策略,比C/C++的要简单不少。不管是条件变量还是互斥锁,synchronized这一个关键字全都搞定。Java每个对象都自带锁,synchronized(obj) { // codeblock } 即可在此代码段保护obj对象;使用obj.wait()、obj.notify()以及obj.nofifyAll()又可以起到条件变量的作用,实在是太方便了。

实现

         代码主要由一个双缓冲队列类、一个生产者类和一个消费者类组成。 需要说明的是,在我的这种实现里,只对写队列做了同步保护,这是因为在我的应用里读写队列交换由消费者控制,这样一来,读队列的使用以及交换全在消费者线程,读队列也就没必要同步了。如果改为定时交换的话,就必须对读队列也加保护了。

双缓冲队列类

         该类以单例模式实现,queue为单例对象。其中,Emp是我们在队列中要缓存的对象类,如果类型多的话可以将双缓冲队列类改造为模板类。成员变量包含两个列表,一个用来读,一个用来写。几个接口,push用来添加新消息,getWriteListSize获取写队列当前大小,getReadList获取读列表,swap用来交换读写列表。
public class DoubleBufferQueue {
	private List readList = new ArrayList();
	private List writeList = new ArrayList();
	private static DoubleBufferQueue queue = new DoubleBufferQueue();
	
	private DoubleBufferQueue() {
		
	}
	
	public static DoubleBufferQueue getInst() {
		return queue;
	}
	
	public void push(Emp value) {
		synchronized (writeList) {			
			writeList.add(value);
		}
	}
	
	public int getWriteListSize() {
		synchronized (writeList) {			
			return writeList.size();
		}
	}
	
	public List getReadList() {
		return readList;
	}
	
	public void swap() {
		synchronized(writeList) {
			List temp = readList;
			readList = writeList;
			writeList = temp;
			
			writeList.clear();	
		}
	}
}

生产者类

        生产者类是一个写线程,负责从服务器读取emp对象的一组实例,放入双缓冲队列的写队列里(代码里与应用相关的部分可以忽略,我临时修改的)。与双缓冲队列相关的调用都在线程函数run里。在调用push函数前,做了个简单的 写入控制:比如当写队列已经缓存了一万个实例时,暂停写入,直到消费者线程用完读队列并交换读写队列。这种方式可能会导致网络吞吐量的降低,但如果能提高消费者效率就可以减少这种降低,比如采用批处理提高Oracle的插入速度。
public class Writer implements Runnable {

	private NetSession session = null;	
	public Writer() {
        session = new NetSession();		
	}
	
	public boolean connect(String server, int port) {
		if (!session.connect(server, port)) {
			return false;
		}
		return true;
	}
	
	public void run() {
		DoubleBufferQueue queue = DoubleBufferQueue.getInst();
		while (!session.valid()) {
			try {
				MsgHead head = session.recvHead();	
				Byte[] buffer = session.recvBody(head);
				if (head.type != EMP)
					continue;
				Emp emp = new Emp();
				emp.deserialize(buffer);
		
				while (queue.getWriteListSize() >= 10000) {
					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				queue.push(emp);
			} catch (NetIOException e) {
				
			}			
		}
	}
}

消费者类

         消费者类是一个读线程,负责将读队列的元素写入数据库(代码做了改动,写入数据库改为写入文件)。读线程不断检测读队列,当读队列有数据时,遍历读队列读取元素写入数据库。这里 需要注意的是,在读线程里做了交换读写队列的控制,只有当读队列为空且写队列大小超过1000时才进行交换。这样做的好处一是可以避免交换频率过高,二是保证一次获取一定量的实例,可以使用数据库的批处理来提高写入效率。另外,在读队列使用完后,记得要清空读队列。
public class Reader implements Runnable {

	public void run() {
		// TODO Auto-generated method stub
		DoubleBufferQueue queue = DoubleBufferQueue.getInst();
		try {			
			while (true) {
				List readList = queue.getReadList();
				while (readList.isEmpty()) {
					try {
						if (queue.getWriteListSize() > 1000) {
							queue.swap();
							readList = queue.getReadList();
						} else {
							Thread.sleep(1);						
						}
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				int counter = 0;
				FileWriter fw = new FileWriter("result.txt", true);   
				for (Emp value : readList) {
					counter++;		
					fw.write(value.toString());
					fw.write("\n");
				}
				fw.close();
				System.out.println("Read: " + counter);
				readList.clear();
			}	
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

应用

        应用部分较为简单,创建两个线程,开启运行即可。读写线程中的读写控制考虑的比较简单,有好想法的朋友欢迎交流,谢谢!
	public static void rwTest() {
		Reader reader = new Reader();
		Thread t1 = new Thread(reader);

		Writer writer = new Writer();
		writer.connect("192.168.1.152", 8000);
		Thread t2 = new Thread(writer);
		
		t1.start();
		t2.start();		
	}
    


你可能感兴趣的:(编程语言)