1 主要流程
read in data:
IO读入(IoProcessor)日志记录、解码、threadPool(IoFilter)业务逻辑处理(IoHandler)
write out data:
业务逻辑处理(IoHandler)日志记录、编码、threadPool(IoFilter) IO写出(IoProcessor)
由以上可以看出,IO读入和IO写出的过程是相反的。
2 线程
这里主要讨论一下Mina中的线程,使用线程,线程池可以提高性能,Mina中就使用了几种线程:
- IoAcceptor/IoConnector线程
- IoProcessor线程
- IoHandler线程
2.1 IoAcceptor/IoConnector线程
IoAcceptor用于监听客户端的连接,每监听一个端口建立一个线程。IoConnector用于与服务端建立连接,每连接一个服务端就建立一个线程。这两种线程都是通过线程池建立的,我们可以在构建对象的时候就指定线程池类型:
public NioSocketAcceptor(Executor executor, IoProcessor<NioSession> processor) {}
public NioSocketConnector(Executor executor, IoProcessor<NioSession> processor) {}
此类线程池的构造在源代码中为(AbstractIoService第168行):
protected AbstractIoService(IoSessionConfig sessionConfig, Executor executor) {
//省略部分代码
if (executor == null) {
this.executor = Executors.newCachedThreadPool();
createdExecutor = true;
} else {
this.executor = executor;
createdExecutor = false;
}
}
由此可见默认的线程池类型为newCachedThreadPool,这是一个可根据需要创建新线程的线程池,在以前构造的线程可用时可以重用它们。
2.2 IoProcessor线程
对于一个IoAcceptor或IoConnector线程对应一个IoProcessor线程用于IO的处理,这个IoProcessor线程从IoProcessor线程池中取出。IoProcessor线程池的大小默认为机器的CPU核数+1,例如双核机器的IoProcessor的线程池大小默认为3,我们可以更改线程池的大小:
IoConnector connector = new NioSocketConnector(9);
IoAcceptor acceptor = new NioSocketAcceptor(9);
如上就把IoProcessor线程池的大小改为9个。
IoProcessor线程池的默认大小在源代码中的定义(SimpleIoProcessorPool第82行):
private static final int DEFAULT_SIZE = Runtime.getRuntime().availableProcessors() + 1;
IoProcessor线程池的构造在源代码中为(SimpleIoProcessorPool第144行):
public SimpleIoProcessorPool(Class<? extends IoProcessor<S>> processorType,
Executor executor, int size) {
//省略部分代码
if (createdExecutor) {
this.executor = Executors.newCachedThreadPool();
} else {
this.executor = executor;
}
}
2.3 IoHandler线程
当我们在过滤器链中没有添加“threadPool”过滤器,则业务逻辑处理和IoProcessor使用同一个线程。如果设置了“threadPool”过滤器,则使用设置的线程池产生线程进行业务逻辑处理,过滤器的配置如下:
acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()));
如上配置之后,IO处理和业务逻辑处理将会使用各自的线程池产生线程使用。如果你的应用每次处理请求的时间较长而又希望应用能够有较好的响应性,那么最好是把处理业务逻辑的任务放到一个新的线程中去执行,而不是在 mina 框架创建的线程中去执行。
2.4 各种线程的产生
1.当 IoAcceptor/IoConnector实例创建的时候,同时一个关联在IoAcceptor/IoConnector上的IoProcessor线程池也被创建。
2. 当IoAcceptor/IoConnector建立套接字(IoAcceptor 的bind()或者是IoConnector 的connect()方法被调用)时,从线程池中取出一个线程,监听套接字端口。
3.当 IoAcceptor/IoConnector监听到套接字上有连接请求时,建立IoSession 对象,从IoProcessor池中取出一个IoProcessor线程执行IO处理。
4. 如若过滤器中配置了“threadPool”过滤器,则使用此线程池建立线程执行业务逻辑(IoHandler)处理,否则使用IoProcessor线程处理业务逻辑。
3 线程查看
举个例子通过jdk自带工具jvisualvm来查看线程:
public class MinaTest {
protected static Logger logger = LoggerFactory.getLogger(MinaTest.class);
private static int PORT = 9999;
public static void main(String[] args) {
try {
// 创建一个非阻塞的server端的Socket
IoAcceptor acceptor = new NioSocketAcceptor();
// 设置过滤器
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
acceptor.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()));
// 设置读取数据的缓冲区大小
acceptor.getSessionConfig().setReadBufferSize(2048);
// 读写通道10秒内无操作进入空闲状态
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
// 绑定逻辑处理器
acceptor.setHandler(new MinaServerHandler());
// 绑定端口
acceptor.bind(new InetSocketAddress(PORT));
logger.info("服务端启动成功... 端口号为:" + PORT);
} catch (Exception e) {
logger.error("服务端启动异常....", e);
e.printStackTrace();
}
}
}
public class MinaServerHandler extends IoHandlerAdapter {
protected static Logger logger = LoggerFactory.getLogger(MinaServerHandler.class);
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
logger.error("服务端发送异常...", cause);
}
public void messageReceived(IoSession session, Object message) throws Exception {
String msg = message.toString();
//如果是quit就关闭session退出
if ("quit".equals(msg)) {
session.close();
}
Date date = new Date();
session.write(date.toString());
}
public void sessionCreated(IoSession session) throws Exception {
logger.info("服务端与客户端创建连接...");
}
}
1.运行MinaTest类,启动服务器端。
2.开始->运行->cmd进入控制台 窗口。
3.输入:telnet 127.0.0.1 9999
4.再重复2、3步骤2次。
5.对jvisualvm的线程视图截图如下:
- 通过以上步骤我们可以看出我们打开了一个服务器端口,并用3个客户端进行连接,下面我们通过以上总结的知识来分析一下服务端产生的线程:
- NioSccketAcceptor为服务器端监听端口9999通过线程池创建的一个线程。
- NioProcessor-1、NioProcessor-2、NioProcessor-3为IoProcessor线程池创建的线程,用来IO处理。
- pool-3-thread-1、pool-3-thread-2、pool-3-thread-3为过滤器配置的线程池创建的线程,用来业务逻辑处理。