MINA2的多线程模型问题探讨

http://scholers.iteye.com/blog/1452780


  最近和一友人沟通,他在一个项目中使用MINA2,他反馈在启动了服务端之后,发现IO阻塞和堆内存一直上升; 

 



JCONSOLE面板中的解释: 
阻塞总数 

Blocked count is the total number of times that the thread blocked to enter or reenter a monitor. I.e. the number of times a thread has been in the java.lang.Thread.State.BLOCKED state. 
当线程试图获取一个内部的对象锁(不是java.util.concurrent库中的锁),而锁被其它线程占有,则该线程进入阻塞状态。 

等待总数 
Waited count is the total number of times that the thread waited for notification. i.e. the number of times that a thread has been in the ava.lang.Thread.State.WAITING or java.lang.Thread.State.TIMED_WAITING state. 
当线程等待另外一个线程通知调度器的一个条件的时候,它自己进入等待状态。在调用Object.wait()或Thread.join()方法,或者等待java.util.concurrent库中的Lock或Condition时,会出现等待状况。 

后这两天我自己本地拿一个简单案例测试了下, 
采用JAVA原生的newCachedThreadPool,以及MINA2自带的 OrderedThreadPoolExecutor 线程池,在性能上没有太大的区别; 
本身他原来的代码中就是使用了一种线程池,将IO线程和工作线程(业务线程)区分开的,所以这个处理的堆内存升高和IO堵塞是正常现象,我本地代码实测,暂用的堆内存并不多,而且是快速回收了,并不影响后续业务; 
下图中大量的thread就是由于使用了ExecutorFilter之后,MINA2服务器端产生的大量线程。 


  在服务端启动的代码中有一段如下: 

Java代码   收藏代码
  1. filterChainBuilder.addLast("threadPool"new ExecutorFilter());  


仔细研究了下JCONSOLE中的阻塞总数,结合本地运行的经验,下图的结果: 



  根据图里面的信息,发现在NioProcessor.java中 
   @Override 
    protected int select(long timeout) throws Exception { 
        return selector.select(timeout); 
    }
 
然后继续跟踪,在AbstractPollingIoProcessor.java类中 
Java代码   收藏代码
  1. private class Processor implements Runnable {  
  2.        public void run() {  
  3.            assert (processorRef.get() == this);  
  4.   
  5.            int nSessions = 0;  
  6.            lastIdleCheckTime = System.currentTimeMillis();  
  7.   
  8.            for (;;) {  
  9.                try {  
  10.                    // This select has a timeout so that we can manage  
  11.                    // idle session when we get out of the select every  
  12.                    // second. (note : this is a hack to avoid creating  
  13.                    // a dedicated thread).  
  14.                    long t0 = System.currentTimeMillis();  
  15.                    int selected = select(SELECT_TIMEOUT);  
  16.                    long t1 = System.currentTimeMillis();  
  17.                    long delta = (t1 - t0);  
  18.   
  19.                    if ((selected == 0) && !wakeupCalled.get() && (delta < 100)) {  
  20.                        // Last chance : the select() may have been  
  21.                        // interrupted because we have had an closed channel.  
  22.                        if (isBrokenConnection()) {  
  23.                            LOG.warn("Broken connection");  
  24.   
  25.                            // we can reselect immediately  
  26.                            // set back the flag to false  
  27.                            wakeupCalled.getAndSet(false);  
  28.   
  29.                            continue;  
  30.                        } else {  
  31.                            LOG.warn("Create a new selector. Selected is 0, delta = "  
  32.                                            + (t1 - t0));  
  33.                            // Ok, we are hit by the nasty epoll  
  34.                            // spinning.  
  35.                            // Basically, there is a race condition  
  36.                            // which causes a closing file descriptor not to be  
  37.                            // considered as available as a selected channel, but  
  38.                            // it stopped the select. The next time we will  
  39.                            // call select(), it will exit immediately for the same  
  40.                            // reason, and do so forever, consuming 100%  
  41.                            // CPU.  
  42.                            // We have to destroy the selector, and  
  43.                            // register all the socket on a new one.  
  44.                            registerNewSelector();  
  45.                        }  
  46.   
  47.                        // Set back the flag to false  
  48.                        wakeupCalled.getAndSet(false);  
  49.   
  50.                        // and continue the loop  
  51.                        continue;  
  52.                    }  
  53.   
  54.                    // Manage newly created session first  
  55.                    nSessions += handleNewSessions();  
  56.   
  57.                    updateTrafficMask();  
  58.   
  59.                    // Now, if we have had some incoming or outgoing events,  
  60.                    // deal with them  
  61.                    if (selected > 0) {  
  62.                        //LOG.debug("Processing ..."); // This log hurts one of the MDCFilter test...  
  63.                        process();  
  64.                    }  
  65.   
  66.                    // Write the pending requests  
  67.                    long currentTime = System.currentTimeMillis();  
  68.                    flush(currentTime);  
  69.   
  70.                    // And manage removed sessions  
  71.                    nSessions -= removeSessions();  
  72.   
  73.                    // Last, not least, send Idle events to the idle sessions  
  74.                    notifyIdleSessions(currentTime);  
  75.   
  76.                    // Get a chance to exit the infinite loop if there are no  
  77.                    // more sessions on this Processor  
  78.                    if (nSessions == 0) {  
  79.                        processorRef.set(null);  
  80.                          
  81.                        if (newSessions.isEmpty() && isSelectorEmpty()) {  
  82.                            // newSessions.add() precedes startupProcessor  
  83.                            assert (processorRef.get() != this);  
  84.                            break;  
  85.                        }  
  86.                          
  87.                        assert (processorRef.get() != this);  
  88.                          
  89.                        if (!processorRef.compareAndSet(nullthis)) {  
  90.                            // startupProcessor won race, so must exit processor  
  91.                            assert (processorRef.get() != this);  
  92.                            break;  
  93.                        }  
  94.                          
  95.                        assert (processorRef.get() == this);  
  96.                    }  
  97.   
  98.                    // Disconnect all sessions immediately if disposal has been  
  99.                    // requested so that we exit this loop eventually.  
  100.                    if (isDisposing()) {  
  101.                        for (Iterator<S> i = allSessions(); i.hasNext();) {  
  102.                            scheduleRemove(i.next());  
  103.                        }  
  104.   
  105.                        wakeup();  
  106.                    }  
  107.                } catch (ClosedSelectorException cse) {  
  108.                    // If the selector has been closed, we can exit the loop  
  109.                    break;  
  110.                } catch (Throwable t) {  
  111.                    ExceptionMonitor.getInstance().exceptionCaught(t);  
  112.   
  113.                    try {  
  114.                        Thread.sleep(1000);  
  115.                    } catch (InterruptedException e1) {  
  116.                        ExceptionMonitor.getInstance().exceptionCaught(e1);  
  117.                    }  
  118.                }  
  119.            }  
  120.   
  121.            try {  
  122.                synchronized (disposalLock) {  
  123.                    if (disposing) {  
  124.                        doDispose();  
  125.                    }  
  126.                }  
  127.            } catch (Throwable t) {  
  128.                ExceptionMonitor.getInstance().exceptionCaught(t);  
  129.            } finally {  
  130.                disposalFuture.setValue(true);  
  131.            }  
  132.        }  
  133.    }  

上述行发生了阻塞行为,这个类是干嘛的呢?这个类是MINA2中去轮训SELECTOR中是否有空闲的,查看一下selector的源码: 
Java代码   收藏代码
  1. /** 
  2. color=red][b]  * Selects a set of keys whose corresponding channels are ready for I/O 
  3.  * operations. 
  4.  * 
  5.  * <p> This method performs a blocking <a href="#selop">selection 
  6.  * operation</a>.  It returns only after at least one channel is selected, 
  7.  * this selector's {@link #wakeup wakeup} method is invoked, the current 
  8.  * thread is interrupted, or the given timeout period expires, whichever 
  9.  * comes first. 
  10.  * 
  11.  * <p> This method does not offer real-time guarantees: It schedules the 
  12.  * timeout as if by invoking the {@link Object#wait(long)} method. </p>[/b][/color] 
  13.  * 
  14.  * @param  timeout  If positive, block for up to <tt>timeout</tt> 
  15.  *                  milliseconds, more or less, while waiting for a 
  16.  *                  channel to become ready; if zero, block indefinitely; 
  17.  *                  must not be negative 
  18.  * 
  19.  * @return  The number of keys, possibly zero, 
  20.  *          whose ready-operation sets were updated 
  21.  * 
  22.  * @throws  IOException 
  23.  *          If an I/O error occurs 
  24.  * 
  25.  * @throws  ClosedSelectorException 
  26.  *          If this selector is closed 
  27.  * 
  28.  * @throws  IllegalArgumentException 
  29.  *          If the value of the timeout argument is negative 
  30.  */  
  31. public abstract int select(long timeout)  
  32. ows IOException;  

仔细的看看注释: 
Selects a set of keys whose corresponding channels are ready for I/O [color=red][/color] 
原来是去轮训IO通道是否空闲,也就是说上述阻塞是发生在这里,也就是说IO通道被轮训发生的阻塞,所以说本文开头的阻塞数量是这样产生的: 
当大量的并发消息过来,MINA2服务端需要轮训IO通道是否OK,OK之后就马上交给工作线程(如果有)去处理。所以从上面看来,这个是正常现象。在同时并发大量消息的时候,MINA2的IO通道还是会有堵塞。 

另外: 
  我们知道:MINA2采用的是责任链模式,在处理过程中插入不同的filter,来进行不同的处理。 
当服务器端启动的时候,会启动默认的I0线程,也就是主线程,MINA2建议是CPU数量+1,如果代码中不增加上述一行,那么如果加入上述的ExecutorFilter之后,工作线程将会和IO线程分开,简单的例子,如果你要发一些消息给服务端,要服务端提交一个事务,如果不采用ExecutorFilter,那么IO线程就会一直等待这个事务结束,才会接着处理下一个任务,否则一直等待,如果采用了ExecutorFilter,那么IO线程会将任务交给ExecutorFilter的线程池,由线程池去处理事务,而IO线程就释放了,这样IO线程非常高效的能够同时处理很多的并发任务。 
用new ExecutorFilter(),缺省使用OrderedThreadPoolExecutor线程池。需要注意的是这个线程池,对应同一个session而言,是顺序执行的.也就是说mina2里面的message处理都是顺序的,不会说出现在消息没有接受完毕之前关闭。 
常见的也有这样的处理:修改成: 
Java代码   收藏代码
  1. filterChainBuilder.addLast("threadPool"new ExecutorFilter(Executors.newCachedThreadPool  
  2.   
  3. ());   




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值