提出背景:
在C/S模式的系统里面,服务器端的主线程,除了要接收消息之外,还要处理消息。这使得主线程的工作量不但很大,而且工作很繁杂。这种情况在软件设计的角度来看,是很不好的:第一,这样让主线程类看起来异常的臃肿和难易阅读,第二,软件设计追求的目标是“尽量让每个类处理的工作都很单一,这样便于以后的调试和进一步对程序的扩展和移植”,这样的设计背离了软件设计中“模块化设计”的原则。
为此,很多程序员会设计一个全局的LinkedBlockingQueue对象来存放服务器主线程所收到的消息,即主线程收到消息之后,不做任何业务逻辑处理,直接put到阻塞队列中去,然后再启动一个处理消息的线程来从阻塞队列中take消息,这样就让服务器端程序大大解耦了,也便于阅读了,当然,最重要的是,效率也提高了不少。
但是在负荷量很大的系统里面,这样做仍然不是最好的办法。上面的解决办法中,仍然存在两个线程竞争一个资源的情况,即仍然存在同步的问题,只是LinkedBlockingQueue在底层进行了处理,在负荷量很大,并且要频繁访问同一个LinkedBlockingQueue对象的情况下,会使得效率较原始情况没有多大的提高。分析一下:主线程每次拿到消息M,都要put到LinkedBlockingQueue对象中,这样在LinkedBlockingQueue底层就要处理一次线程同步问题,当工作量很大很频繁的时候,这样的操作未免就显得有些累赘了,浪费了很多的时间和资源。同样在处理线程,每次take一个消息M,就会处理一次线程同步,同样降低了效率。注意,只是在负荷量很大的情况下(如中国移动CMPP短信网关),这样的弱点才会很明显地暴露出来。
问题分析:
暴露出了问题,就需要我们想办法去解决,通过上面的分析,发现瓶颈在于,我们是对同一个LinkedBlockingQueue进行的操作,才会引起这么多的滞后。那么能不能试着去用两个LinkedBlockingQueue对象进行操作呢?一个专门给主线程put,另外一个专门给处理线程take和处理,当他们处理完了,再来交换?这样的话,就保障了基本上在每个时刻,每个线程都是在处理自己独自占有的资源,没有引发同步问题。这称之为:双缓冲队列。
当然,上面的解决方案是在一种很理想的情况下,即两个队列,在同一时刻被放满和取空,这样他们在程序运行过程中所达到的效率是最高的,因为没有任何消息被延后处理。所有消息都按照原定计划被处理了。现实情况往往没有这般顺利,但是上面的方案也不失为我们提供了一种解决思路,这其中还涉及到很多的处理机制。为了分析方便,我们暂且将两个LinkedBlockingQueue对象分别称之为LP(主线程控制的专门put的队列),LT(处理线程控制的,专门take的队列)。
第一, LP和LT队列什么时候才进行交换?肯定是在LT将所有的消息处理完了之后才进行交换,如果LT里面的消息都没有处理完就被交换了,那有可能LT里面的消息有些几年甚至永远不会被处理,这样的系统是无法投入使用的。
第二, 如果在LT处理完了,LP中还没有收到任何消息会怎么办?这是一种很极端的情况,但是也必须考虑到,延伸一下,如果LT里面的消息处理完了,而LP里面却只有一两条消息,该怎么办?要继续交换,还是等待一段时间以后再进行交换?在这里,笔者采用的是前者。目的是为了让消息都能及时地被处理。
问题解决:
首先我们来看看不使用双缓冲机制的程序:
package cn.netjava.lbq.copy;
//玩具类
public class Toy {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package cn.netjava.lbq.copy;
//在线程中生产玩具,放入单缓冲队列
public class Factory extends Thread{
public void run(){
while(true){
Toy t = new Toy ();
t.setName("玩具");
try {
Tools.lT.put(t);
System.out.println("生产出一个玩具");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package cn.netjava.lbq.copy;
//从单缓冲队列中取出玩具对象
public class Kid extends Thread {
long time1 = System.currentTimeMillis();
int count = 0;
public void run() {
while(true){
try {
Tools.lT.take();
count++;
//计算取100000个所需要的时间
if(count==100000){
System.out.println(System.currentTimeMillis()-time1);
Thread.sleep(100000);
}
System.out.println("玩完一个玩具");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package cn.netjava.lbq.copy;
//存放玩具对象的的队列
import java.util.concurrent.LinkedBlockingQueue;
public class Tools {
public static LinkedBlockingQueue<Tool>lT = new LinkedBlockingQueue<Tool>(10000);
}
package cn.netjava.lbq.copy;
public class MainTest {
/**
* @param args
*/
public static void main(String[] args) {
Factory f = new Factory();
f.start();
Kid k = new Kid();
k.start();
}
}
取出100000个玩具的时间为:3609ms
再来看看使用双缓冲队列的情况,toy类就不再贴出了。
看其他类,中间做了一些改动
package cn.netjava.lbq;
import java.util.concurrent.LinkedBlockingQueue;
public class Tools {
public static LinkedBlockingQueue<Tool>lT = new LinkedBlockingQueue<Tool>(10000);
public static LinkedBlockingQueue<Tool>lP = new LinkedBlockingQueue<Tool>(10000);
}
package cn.netjava.lbq;
public class Kid extends Thread {
long time1 = System.currentTimeMillis();
int count = 0;
public void run() {
while(true){
try {
Tools.lT.take();
count++;
if(count==10000){
System.out.println(System.currentTimeMillis()-time1);
Thread.sleep(100000);
}
System.out.println("玩完一个玩具");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package cn.netjava.lbq;
import java.util.concurrent.LinkedBlockingQueue;
public class Factory extends Thread{
public void run(){
while(true){
Toy t = new Toy ();
t.setName("玩具");
try {
Tools.lP.put(t);
System.out.println("生产出一个玩具");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package cn.netjava.lbq;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 定义一个双缓冲队列
* @author Irving
* @time 2009-9-16----上午10:25:49
*
*/
public class DoubleBufferList {
private LinkedBlockingQueue<Object> lP;
private LinkedBlockingQueue<Object> lT;
private int gap;
/**
* 构造方法
* @param lP 用来存放对象的阻塞队列
* @param lT 用来取对象的阻塞队列
* @param gap 交换的间隔
*/
public DoubleBufferList(LinkedBlockingQueue lP
,LinkedBlockingQueue lT,int gap){
this.lP=lP;
this.lT=lT;
this.gap=gap;
}
public void check(){
Runnable runner = new Runnable(){
public void run() {
while(true){
if(lT.size()==0){
synchronized(lT){
synchronized(lP){
lT.addAll(lP);
}
lP.clear();
}
} try {
Thread.sleep(gap);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runner);
thread.start();
}
}
package cn.netjava.lbq;
public class MainTest {
/**
* @param args
*/
public static void main(String[] args) {
Factory f = new Factory();
f.start();
Kid k = new Kid();
k.start();
new DoubleBufferList(Tools.lP,Tools.lT,1).check();
}
}
同样100000个玩具对象,使用的时间是3469ms,大约节约了200ms
在使用双缓冲队列的时候,要注意调试监测线程的监控时间间隔和阻塞队列的大小,这些都是影响运行速度的关键因素。
分享到:
相关推荐
双缓冲队列的设计与实现,多线程,c语言实现,用于提高程序运行效率
C#版服务端双缓冲队列,采用空间换时间的思想,解决频繁争夺锁之间的时间开销
服务器端双缓冲队列s/c,简单的实现了服务器端的队列实现
主要介绍了C++数据结构与算法之双缓存队列实现方法,结合实例形式分析了双缓存队列的原理、实现方法与相关注意事项,需要的朋友可以参考下
VC双缓冲实现方法,双缓冲绘图,详细代码。
目前STM32家族中有些系列支持DMA的双缓冲模式,比如STM32F2/STM32F4/STM32F7等系列。尤其随着人们对STM32F4/F7系列应用不断拓宽和加深,在设计中运用到DMA双缓冲的场合也越来越多。STM32芯片中的DMA又可分为两大类,...
双缓冲技术教程加源码,解决重绘闪屏现象,听起来很深澳,学会了就简单
BitBlt+双缓冲BitBlt+双缓冲BitBlt+双缓冲
环形缓冲区读写操作的分析与实现,以及在并发条件下如何控制竞争
1、双缓冲绘图技术概述 原理:将一般的在CDC中绘图改成在一内存设备中绘图(CDC型变量,一般命名为pMemDC),然后使用BitBlt()函数将pMemDC中的图像复制到CDC中; 步骤: ①创建内存设备CDC *pMemDC;关联设备...
在keil环境下基于stm32F407的DMA的双缓冲的ADC数据采集,这是一个完整的工程,可以直接下载使用
双缓冲技术.rar 包含代码+文档+各种资料 我自己整理的 双缓冲技术.rar 包含代码+文档+各种资料 我自己整理的 双缓冲技术.rar 包含代码+文档+各种资料 我自己整理的 双缓冲技术.rar 包含代码+文档+各种资料 我自己...
VC关于双缓冲绘图的实现.doc vc双缓冲VC++双缓冲实现方法.pdf 实例解说双缓冲.rar 我认为非常好的双缓冲技术实例,,,,,
本文档主要讲述的是Java双缓冲技术;Java的强大特性让其在游戏编程和多媒体动画处理方面也毫不逊色。在Java游戏编程和动画编程中最常见的就是对于屏幕闪烁的处理。本文从J2SE的一个再现了屏幕闪烁的Java ...
stm32usb发送双缓冲stm32usb发送双缓冲stm32usb发送双缓冲stm32usb发送双缓冲stm32usb发送双缓冲stm32usb发送双缓冲
vc 双缓冲技术
VC 双缓冲 VC 双缓冲 VC 双缓冲 VC 双缓冲 VC 双缓冲 VC 双缓冲 VC 双缓冲
一般应用开启双缓冲和不开基本没有有明显区别,此资源没有技术性相关代码,仅使用自带的双缓冲属性,用极端的情况让你感受到在特殊情况下开启双缓冲和不开启的明显区别。
stm32定时器+ADC+DMA+双缓冲 实现数据采集