Java中BlockingQueue性能瓶颈问题

最近调研一个数据分析模块中的性能问题,花费将近两周时间。期间做了许多Enhancement,而最后关于总线的性能瓶颈定位和处理值得记录一下。

首先是场景描述,一个典型的生产者消费者环境。总线Bus接收来自不同方向的数据,根据数据的分类,发送给相关订阅者。在数据交互过程中,使用JDK中的阻塞队列ArrayBlockingQueue:生产者的提供Object存入总线中的BlockingQueue;总线经计算判断后将Object存入对应消费者的BlockingQueue。为了不丢失数据,当某个消费者队列满时,总线的put操作会阻塞直到该消费者可用。

Java中BlockingQueue性能瓶颈问题_第1张图片

性能分析

通过JRat(http://blog.csdn.net/chifengxin/article/details/6750619)、JProbe工具显示,CPU利用率主要消耗在消费者处。耗费大量精力优化消费者,包括增加多线程处理等。当消费者提高到极限后,通过Unix命令发现总线Bus线程居然占满到一个核(top命令后,shift+h查看单核线程列表,找到占用cpu最多的java线程,将该id用计算器转换成16进制。然后执行JDK的jstack得到堆栈信息,在堆栈中查找该16进制线程id对应的线程)。

于是Focus在总线Bus。最后结果是,此处占用CPU的竟然不是普通处理操作,而是在BlockingQueue的put方法。在Queue总是未满的情况,put方法仍然占用大量的资源。

验证:

写了个模拟线程验证,单线程占满一核CPU后,BlockingQueue极限写速度为1500K/s。因而加上其它处理,导致本程序中Bus的性能极限只有300K/s。

解决:

1、 在JDK提供的BlockingQueue中,ArrayBlockingQueue是效率最高和消耗最少的。尝试换用双缓冲队列(参照:http://www.iteye.com/topic/477412),提高很小

2、 考虑到DougLea对锁的优化,放弃重写queue

3、 最终解决方案-Pack包裹

封装了一个简单数组容器继承原Object接口(或者使用其它任意的JDK容器):

public interface IPackObject extends IObject {

       public boolean add(IObject obj);

       public IObject get(int index);

       public int size();

       public boolean full();

}

该容器的构造方法需要传入一个capacity参数作为数组values的初始大小;实现数组写入和读取的方法。使用时,在往BlockingQueue放入对象前,先把对象放入该容器,直到容器满后才将容器put到BlockingQueue,从而避免在put过程中的资源消耗。消费者取到后,加上判断IObject是否是instanceof IPackObject,如果是容器则循环取出对象后再操作。

理论上,当容器容量为100时,在BlockingQueue上的资源消耗将减少99%。

注:需要考虑当容器处于不满状态的超时。最简单的方法是定期(如每秒)将所有未满的容器put到queue。


附:简易非安全对象容器

public class ObjectArray<E>
{
    /**
     * A simple Object container 
     */
    private Object []values;
    private int capacity;
    private int size;
    private int cursor;
    
    public ObjectArray()
    {
        this(10);
    }
    
    public ObjectArray(int capacity)
    {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.capacity = capacity;
        values = new Object[capacity];
    }
    
    public void add(E object)
    {
        if(size >= capacity)
        {
            capacity *=2;
            Object [] oldValues = values;
            values = new Object[capacity];
            System.arraycopy(oldValues, 0, values, 0, oldValues.length);
        }
        values[size++] = object;
    }
    
    public int size()
    {
        return size;
    }
    
    public void resetCursor()
    {
        this.cursor=0;
    }
    
    public boolean hasNext()
    {
        return cursor<size; 
    }
    
    public E next()
    {
        return (E) values[cursor++];
    }
}


你可能感兴趣的:(java,jdk,多线程,数据分析,object,interface)