Java双缓冲队列实现
来源:互联网 发布:迪联软件 编辑:程序博客网 时间:2024/05/17 08:46
前言
在某一模块中,需要将网络接收到的数据存入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<Emp> readList = new ArrayList<Emp>();private List<Emp> writeList = new ArrayList<Emp>();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<Emp> getReadList() {return readList;}public void swap() {synchronized(writeList) {List<Emp> 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 blocke.printStackTrace();}}queue.push(emp);} catch (NetIOException e) {}}}}
消费者类
消费者类是一个读线程,负责将读队列的元素写入数据库(代码做了改动,写入数据库改为写入文件)。读线程不断检测读队列,当读队列有数据时,遍历读队列读取元素写入数据库。这里需要注意的是,在读线程里做了交换读写队列的控制,只有当读队列为空且写队列大小超过1000时才进行交换。这样做的好处一是可以避免交换频率过高,二是保证一次获取一定量的实例,可以使用数据库的批处理来提高写入效率。另外,在读队列使用完后,记得要清空读队列。public class Reader implements Runnable {public void run() {// TODO Auto-generated method stubDoubleBufferQueue queue = DoubleBufferQueue.getInst();try {while (true) {List<Emp> 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 blocke.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();}
0 0
- Java双缓冲队列实现
- 实现一个双缓冲队列
- 实现一个双缓冲队列
- 实现一个双缓冲队列
- 实现一个双缓冲队列
- 双缓冲队列
- 双缓冲队列尝试
- 双缓冲队列
- 双缓冲队列
- 双缓冲队列
- 环形双缓冲队列
- 双缓冲队列
- 使用Java实现双缓冲绘图
- 服务器应用--双缓冲队列
- 服务器端利器--双缓冲队列
- 服务器应用--双缓冲队列
- 服务器应用--双缓冲队列
- 服务器应用--双缓冲队列
- MySQL数据库出现The server quit without updating PID file.
- 推荐算法学习2-MXNET 实现movielen 融合个性化推荐-续-加入CNN文本处理
- Cg Programming/Unity
- CVE-2012-0158浅析-word栈溢出漏洞
- 解决MDK5在调试中崩溃,提示“IDE已停止工作”的一种方法
- Java双缓冲队列实现
- Android异步下载图片并且缓存图片到本地
- Handler
- ICE的使用
- UITableView拖动时计算页码 & 往上拖拉时自动加载
- MySQL提示:The server quit without updating PID file问题的解决办法
- nyoj 104最大和
- 调用Metasploit RestFul接口,解决证书问题
- linux下查看mysql启动状态