[介绍]
去年(2014),对我们产品中的一个模块,通过使用BlockingQueue,性能提升很多。我觉得有些借鉴意义,这里分享给大家。可以说,此更改是所有java对block q有所了解的人都能够做到的,但是实际工作中确实可能碰到类似的情况。
简而言之:用BlockingQueue替换原有SynchronizeQueue块。更改后,该模块的性能从50msg/second, 提升到700 msg/second!
[代码]
直接看代码比较清楚。看完如下代码,你可能会莞尔一笑,so easy. 那就结束了吧。不过我下面将给出如何发现这个问题,以及自己做的一个小例子来验证性能提升。
修改前
private final Queue<ProtocolMessageEvent> queue = new LinkedList<ProtocolMessageEvent>(); 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<ProtocolMessageEvent> queue = new LinkedBlockingQueue<ProtocolMessageEvent>(); 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<ProtocolMessageEvent> queue = new LinkedList<ProtocolMessageEvent>(); 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<ProtocolMessageEvent> queue = new LinkedBlockingQueue<ProtocolMessageEvent>(); 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; } }