20_[实践]用BlockingQueue替换原有SynchronizeQueue块,带来的性能巨大提升

[介绍]

去年(2014),对我们产品中的一个模块,通过使用BlockingQueue,性能提升很多。我觉得有些借鉴意义,这里分享给大家。可以说,此更改是所有java对block q有所了解的人都能够做到的,但是实际工作中确实可能碰到类似的情况。
 
简而言之:用BlockingQueue替换原有SynchronizeQueue块。更改后,该模块的性能从50msg/second, 提升到700 msg/second!
 
 
最新状态保存于有道笔记:  http://note.youdao.com/share/?id=15ee7bfeb85e7af57e85e7962350d67d&type=note
 
[代码]
直接看代码比较清楚。看完如下代码,你可能会莞尔一笑,so easy. 那就结束了吧。不过我下面将给出如何发现这个问题,以及自己做的一个小例子来验证性能提升。
 
 
修改前
      private final Queue queue  = new LinkedList();
             while (true)
             {
                 synchronized (this.queue)
                 {
                     final ProtocolMessageEvent event = this.queue.poll();
                     // TODO does the sync have to be held during message processing?
                     if (event != null)
                     {
                         if (event.getProtocolMessage().getType().equals("5"))
                         {
                             break;
                         }
                         handleProtocolMessageEvent(event);
                     }
                 }
                 Thread.sleep(1);
             }
 修改后
    private final LinkedBlockingQueue queue  = new LinkedBlockingQueue();
             while (true)
             {
                 ProtocolMessageEvent event = queue.take();
 
                 if (event != null)
                 {
                     if (event.getProtocolMessage().getType().equals("5"))
                     {
                         break;
                     }
                     handleProtocolMessageEvent(event);
                 }
             }
 [如何发现]
有几种方式都可以发现这个问题
1) 作为一个老程序员,review代码的时候就可以发现这个问题。其实这个实在是太简单直接了
2) 做自动化性能测试时,发现性能一直上不去,在30 msg/second徘徊。通过jvisualvm,看各个线程状态,会发现此线程sleep时间比较长。
注意:应用中有很多线程,要发现bottleneck线程,很多时候比较困难,因为要查看所有线程
 
[扩展]
1. 学习新知识很重要,尤其是jdk的重要新feature.此类问题就永远不会出现。因为此应用使用的jdk1.6. concurrent framework 是1.5就引入了的。
2. 要有自动化的performance测试,这样修改程序以后,可以很容易知道性能提升有多少。
 
[简单例子]
为了加深认识,写了一个简化的验证程序。该程序有278行,解释了两种的性能差别。
使用synchronizeQ块的方式,最快不到1000msg/s。而使用blockingQ到了20,000msg/s还完全没问题。
 
运行结果如下
$$$$ synchronize Q test $$$$
stats result -- acturely duration:9.951 throughput:502.4620641141594 sending speed:500.0
stats result -- acturely duration:10.207 throughput:979.7198001371607 sending speed:999.30048965724
stats result -- acturely duration:20.362 throughput:982.2217856792064 sending speed:1998.201618543311
$$$$ blocking Q test $$$$
stats result -- acturely duration:9.905 throughput:504.7955577990914 sending speed:499.7501249375312
stats result -- acturely duration:9.975 throughput:5012.5313283208025 sending speed:5002.501250625313
stats result -- acturely duration:9.993 throughput:10007.004903432402 sending speed:9997.000899730081
stats result -- acturely duration:9.926 throughput:20149.10336490026 sending speed:20128.824476650563
 
 
note:作为测试代码,200+行,略长
note:它引用了我写的一个batch发送的框架程序,极大方便了设定发送速度。此程序非常方便,有空我会分享出来。
代码贴在下面 - 有点冗长:
package baoying.perf.trtnfix;

import java.util.Date;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

import baoying.util.PerfInvoker;
import baoying.util.PerfInvoker.PerfInvokerCallback;

/**
 result: 20150711
$$$$ synchronize Q test $$$$
stats result -- acturely duration:9.951 throughput:502.4620641141594 sending speed:500.0
stats result -- acturely duration:10.207 throughput:979.7198001371607 sending speed:999.30048965724
stats result -- acturely duration:20.362 throughput:982.2217856792064 sending speed:1998.201618543311
$$$$ blocking Q test $$$$
stats result -- acturely duration:9.905 throughput:504.7955577990914 sending speed:499.7501249375312
stats result -- acturely duration:9.975 throughput:5012.5313283208025 sending speed:5002.501250625313
stats result -- acturely duration:9.993 throughput:10007.004903432402 sending speed:9997.000899730081
stats result -- acturely duration:9.926 throughput:20149.10336490026 sending speed:20128.824476650563
 *
 */
public class PollingVSBlockingQ {


	/**
	 * @param args
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException {

		
		PollingVSBlockingQ vs = new PollingVSBlockingQ();
		
		System.out.println("$$$$ synchronize Q test $$$$");
		final LogicInterface pollingLogic = new PollingLogic();
		vs.runTest(pollingLogic, 500, 10); //500 msg per second, 10 seconds
		vs.runTest(pollingLogic, 1000, 10);
		vs.runTest(pollingLogic, 2000, 10);

		System.out.println("$$$$ blocking Q test $$$$");
		final LogicInterface blockingLogic = new BlockingQLogic();
		vs.runTest(blockingLogic, 500, 10);
		vs.runTest(blockingLogic, 5000, 10);
		vs.runTest(blockingLogic, 10000, 10);
		vs.runTest(blockingLogic, 20000, 10);

	}
	
	public void runTest(final LogicInterface logic, final int ratePerSec, final int duarationInSec) throws InterruptedException{
		Thread feedThread = new Thread(new Runnable() {
			public void run() {
				try {
					logic.feedQ(ratePerSec,	duarationInSec);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}, "feedThread");

		Thread consumeThread = new Thread(new Runnable() {
			public void run() {
				try {
					logic.consumeQ();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}, "consumeThread");

		feedThread.start();
		consumeThread.start();
		
		feedThread.join();
		consumeThread.join();
		
	}

}

interface LogicInterface{
	public void feedQ(final int ratePerSec, final int duarationInSec) throws InterruptedException;
	public void consumeQ() throws InterruptedException;
}


class PollingLogic implements LogicInterface{

	int ratePerSec;
	int duarationInSec;

	Date start = null;
	Date endSending = null;
	Date recEnd = null;

	private final Queue queue = new LinkedList();

	public void feedQ(final int ratePerSec, final int duarationInSec) throws InterruptedException {
		this.ratePerSec = ratePerSec;
		this.duarationInSec=duarationInSec;

		PerfInvokerCallback c = new PerfInvokerCallback() {

			@Override
			public void execute(long seq) {
				synchronized (queue) {

					ProtocolMessageEvent e = new ProtocolMessageEvent(new Type(
							"3"));
					queue.add(e);
				}

			}
		};
		PerfInvoker ferfInvoker = new PerfInvoker(ratePerSec, duarationInSec, c);

		start = new java.util.Date();
		ferfInvoker.execute();
		endSending = new java.util.Date();

	}

	public void consumeQ() throws InterruptedException {

		int iReceived = 0;
		while (true) {
			synchronized (this.queue) {
				final ProtocolMessageEvent event = this.queue.poll();
				// TODO does the sync have to be held during message processing?
				if (event != null) {
					iReceived++;
					if (iReceived == ratePerSec * duarationInSec) {
						recEnd = new java.util.Date();
						if (endSending == null) {
							Thread.sleep(1000);
						}
						SimpleStatsHelper stats = new SimpleStatsHelper(
								ratePerSec, duarationInSec, start, endSending,
								recEnd);
						stats.calcStatsResult();
						break;
						// System.exit(0);

					}
					if (event.getProtocolMessage().getType().equals("5")) {
						break;
					}
					handleProtocolMessageEvent(event);
				}
			}
			Thread.sleep(1);
		}
	}

	private void handleProtocolMessageEvent(ProtocolMessageEvent event) {

		int x = 6;
		if (!(x * System.currentTimeMillis() > 200)) {
			System.out.println("impossible");
		}

	}
}

class BlockingQLogic implements LogicInterface{

	private final BlockingQueue queue = new LinkedBlockingQueue();

	int ratePerSec;
	int duarationInSec;

	Date start = null;
	Date endSending = null;
	Date recEnd = null;

	public void feedQ(final int ratePerSec, final int duarationInSec) throws InterruptedException {
		this.ratePerSec = ratePerSec;
		this.duarationInSec=duarationInSec;

		PerfInvokerCallback c = new PerfInvokerCallback() {

			@Override
			public void execute(long seq) {

				ProtocolMessageEvent e = new ProtocolMessageEvent(new Type("3"));
				queue.add(e);
			}

		};
		PerfInvoker ferfInvoker = new PerfInvoker(ratePerSec, duarationInSec, c);

		start = new java.util.Date();

		ferfInvoker.execute();
		endSending = new java.util.Date();
		

	}

	public void consumeQ() throws InterruptedException {

		int iReceived = 0;
		while (true) {
			final ProtocolMessageEvent event = this.queue.take();
			// TODO does the sync have to be held during message processing?
			if (event != null) {
				iReceived++;
				if (iReceived == ratePerSec * duarationInSec) {
					recEnd = new java.util.Date();

					//TODO refactor - bad code to wait endSeding be assigned value in sending thread.
					//how to refactor 
					if (endSending == null) {
						Thread.sleep(1000);
					}
					
					SimpleStatsHelper stats = new SimpleStatsHelper(ratePerSec,
							duarationInSec, start, endSending, recEnd);
					stats.calcStatsResult();
					
					//TODO as bad as above, too.
					endSending =null;
					
					break;
					// System.exit(0);

				}
				if (event.getProtocolMessage().getType().equals("5")) {
					break;
				}
				handleProtocolMessageEvent(event);
			}

		}
	}

	private void handleProtocolMessageEvent(ProtocolMessageEvent event) {

		int x = 6;
		if (!(x * System.currentTimeMillis() > 200)) {
			System.out.println("impossible");
		}

	}
}

class ProtocolMessageEvent {

	Type _t;

	ProtocolMessageEvent(Type t) {
		_t = t;
	}

	public Type getProtocolMessage() {
		return _t;
	}

}

class Type {

	String _t;

	Type(String t) {
		_t = t;
	}

	String getType() {
		return _t;
	}

}
 
 

 

你可能感兴趣的:(Java)