📜  java异步编程示例——Java(1)

📅  最后修改于: 2023-12-03 15:02:04.691000             🧑  作者: Mango

Java异步编程示例

在大型应用中,I/O 操作通常是瓶颈所在。在传统的同步编程模型中,I/O 操作会阻塞程序执行线程,导致程序响应延迟。异步编程模型则能够在 I/O 操作进行的同时,让程序执行其他任务,从而提高程序的吞吐率和响应时间。本文将介绍 Java 异步编程的基本概念和示例,帮助程序员更好地理解异步编程。

异步编程基本概念

异步编程基于事件驱动模型,即程序在调用 I/O 操作时,不会被阻塞,而是会注册 I/O 完成事件的回调函数,在 I/O 操作完成后自动被调用。这种模型需要异步编程框架的支持,常见的有 Java 原生的 NIO 以及第三方的 Netty 等。

Java NIO 支持异步 I/O 操作,通过 Selector、Channel、Buffer 等类实现异步编程。下面是一个简单的 NIO 示例,利用 Selector 轮询监听 I/O 事件,实现非阻塞 I/O:

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioServer {
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);
    
    public NioServer(int port) throws Exception {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        serverChannel.socket().bind(new InetSocketAddress(port));
        
        selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }
    
    public void listen() throws Exception {
        while (true) {
            selector.select();
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove();
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel channel = server.accept();
                    channel.configureBlocking(false);
                    channel.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    channel.read(buffer);
                    buffer.flip();
                    channel.write(buffer);
                    buffer.clear();
                    channel.close();
                }
            }
        }
    }
    
    public static void main(String[] args) throws Exception {
        NioServer server = new NioServer(8080);
        server.listen();
    }
}

在该示例中,服务器的 accept 和 read 操作均采用了非阻塞方式,当有事件发生时触发 key 对应的回调函数。

除了使用 Java 原生的异步编程框架,也可以使用更加高级的第三方异步编程框架。例如,Netty 提供了更加简洁的 API 和更加详细的文档支持,可以更加方便地进行异步编程。下面是一个使用 Netty 的简单示例:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class NettyServer {
    public void listen(int port) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) {
                            ByteBuf in = (ByteBuf) msg;
                            ctx.write(in);
                        }
                        
                        @Override
                        public void channelReadComplete(ChannelHandlerContext ctx) {
                            ctx.flush();
                        }
                        
                        @Override
                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
                            cause.printStackTrace();
                            ctx.close();
                        }
                    });
                }
            });
            
            bootstrap.bind(port).sync().channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    
    public static void main(String[] args) throws Exception {
        NettyServer server = new NettyServer();
        server.listen(8080);
    }
}

在该示例中,使用了 Netty 提供的 EventLoopGroup、ServerBootstrap 等类作为异步编程的基础。ChannelInitializer 则为每个客户端连接建立一个 Channel,处理下发的事件并最终发送完整的响应。

异步编程使用场景

异步编程适用于大量 I/O 密集、计算较少的场景,例如网络通信、文件读写、数据库访问等。比较常见的使用场景有:

  • 单机 Web 服务器
  • 大型分布式系统
  • 数据库连接池
  • 高并发网络应用
  • 长连接实时通信

异步编程能够提高应用的吞吐率和响应时间,从而能够在较小的硬件配置下支撑更多的用户访问。因此,在上述场景下都可以使用异步编程,提高应用性能和可用性。

总结

异步编程适用于大量 I/O 密集、计算较少的场景,通过事件驱动模型和回调函数,能够提高应用的吞吐率和响应时间,支持更加高并发的用户访问。Java 原生的 NIO 和 Netty 等第三方框架均提供了异步编程的支持,能够简化异步编程的实现。