netty中的异步以及promise分析


Netty,所有的网络I/O操作都是异步执行的。
netty 中的API操作,大都是异步的,会返回一个future,如有必要,我们需要监听该future的处理结果 或者同步阻塞等待异步的返回结果。
这个基础就是多线程交互:一个线程发生了变化,如何通知另一个线程呢?
下面看一下常见的方法或问题

客户端意外退出:close、closeFuture

       NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventExecutors)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.SINGLE_EVENTEXECUTOR_PER_GROUP, false)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                    }
                }).remoteAddress("127.0.0.1", 6666);
        try {
            ChannelFuture future = bootstrap.connect();
            Channel channel = future.channel();
            channel.writeAndFlush("44"); // 建立连接后发送消息
             System.out.println("send ");// 打印
            channel.closeFuture();// 使用完后,关闭channel
        } finally {
            eventExecutors.shutdownGracefully();
        }

运行上面代码,可能出现:客户端程序运行一段时间自动退出。

  1. main线程调用connect,真正执行连接逻辑的是NIO线程,成功与否还要看返回的结果。如果还没有建立,就用channel发送,可能导致异常。
  2. 假设发送成功,那客户端必然关闭。closeFuture其实是一个通知,只有该channel被关闭时,它内部才会被赋值。main线程调用channel.closeFuture()后, 就执行到了finally块,自动释放了线程池。

正确代码

        try {
            ChannelFuture future = bootstrap.connect();
            // sync,同步阻塞。因为必须要得且channel建立连接后才能后续操作,所以阻塞即可
            Channel channel = future.sync().channel();
            channel.writeAndFlush("44");
            System.out.println("send ");
             // 这里可以阻塞main线程,或者 异步等待结果,main线程可以去处理其他事情。
            channel.closeFuture().sync();
        } finally {
            eventExecutors.shutdownGracefully();
        }
或者
  ChannelFuture future = bootstrap.connect();
            Channel channel = future.sync().channel();
            channel.writeAndFlush("44");
            System.out.println("send ");
            
            // 监听关闭事件。一旦channel关闭,就释放池子
            channel.closeFuture().addListener(future1 -> {
                eventExecutors.shutdownGracefully();
            });
            // main线程可以做其他事情。。

write或writeandFlush

不知道你是不是碰到过这样的场景?
和服务端扯皮时,你拿着调用链路图给他看,你说你们接口是10s,服务端把他们的日志拿出来,一摊手:就3s啊,没准是网络波动了。你看他又把锅推到了网络上了。下图。
在这里插入图片描述
其实,再具体的链路如下图。服务端只统计了他们的业务耗时,没有包含服务框架本身的调度和处理时间。 在做链路统计时,服务端也需要把这部分时间算上。
在这里插入图片描述

然后服务端认了,马上整改。可是写代码的是外包仔,对netty不熟悉。
他埋点这么做的:

ctw.writeAndFlush(xx); //外包仔认为此处已经发送出去了。
记录埋点;

调用 writeAndFlush 并不代表消息已经发送到网络上,它仅仅是一个异步的消息发送操作,调用 writeAndFlush 之后,Netty 会执行一系列操作,最终将消息发送到网络上。所以要监听他的结果。
在这里插入图片描述

后来经过毒打,终于正确了:(注意执行andListener监听的线程是NIO线程,也就是NIO线程记录的埋点。)

future = ctw.writeAndFlush(xx);
future.addListener(f -> {
	记录埋点;
});

promise的get、addlistener是如何实现的

netty为了方便多线程交互,写了一个类叫promise,他和java的future一样。

    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        // 让nio线程去通知listener
        DefaultPromise<String> promise = new DefaultPromise<>(group.next());
        promise.addListener(future -> {
            // 当前线程应该是NIO线程。
            System.out.println(Thread.currentThread().getName() + "promise listener:" + future.get());
        });

        new Thread(() -> {
            try {
                String result = promise.get();
                System.out.println(Thread.currentThread().getName() + result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                promise.cause().printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                String result = promise.get();
                System.out.println(Thread.currentThread().getName() + result);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
                promise.cause().printStackTrace();
            }
        }).start();
        Thread.sleep(1000);
        System.out.println("其他线程都被阻塞了。main没有被阻塞。只有调用了setSuccess后,其他阻塞的线程才能成功");
        System.out.println(promise.isSuccess());
        promise.setSuccess("success");
        System.out.println(promise.isSuccess());
    }

promise.get和listener,都会被阻塞直到promise被赋值。
先分析addlistener.
如何做到的。看promise.setSuccess(“success”);源码。
发现用AtomicReferenceFieldUpdater把promise里面的result升级为原子操作。然后用CAS看result是否赋值过。一旦赋值成功,就去调用notifyListeners唤醒。

    private boolean setValue0(Object objResult) {
        if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
            RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
            if (checkNotifyWaiters()) {
                notifyListeners();
            }
            return true;
        }
        return false;
    }

一直到唤醒的底层。循环去调用operationComplete接口。
奥原来如此,addListener时,每个listener都要实现operationComplete接口。

   private static void notifyListener0(Future future, GenericFutureListener l) {
        try {
            l.operationComplete(future);
        } catch (Throwable t) {
            if (logger.isWarnEnabled()) {
                logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t);
            }
        }
    }

那promise.get呢?
首先竞争promise锁,竞争成功就去循环wait。其他没有竞争成功的就去阻塞队列等待,进入blocked状态。

  synchronized (this) {
            while (!isDone()) {
                incWaiters();
                try {
                   wait(0);
                } finally {
                    decWaiters();
                }
            }
        }

等等,进入blocked的线程那谁去唤醒他啊?还记得checkNotifyWaiters方法吗?
它里面不仅判读了get的调用次数,还判断了是否有listener。
如果有get调用的话,他就是notifyAll,唤醒那些竞争锁失败的线程。

  private synchronized boolean checkNotifyWaiters() {
        if (waiters > 0) {
            notifyAll();
        }
        return listeners != null;
    }

参考:netty进阶指南-李林峰

本文作者:WKP9418
本文连接:https://blog.csdn.net/qq_43179428/article/details/140575847

  • 21
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值