美文网首页
boy-learning-netty | 11 Netty 的哪

boy-learning-netty | 11 Netty 的哪

作者: BruceOuyang | 来源:发表于2019-10-31 16:02 被阅读0次

    相关源码:boy-learning-netty
    个人博客:http://bruce.bugmakers.club
    内容来自《极客时间 - Netty源码剖析与实战》

    在程序开发中,我们经常用多线程技术来提高程序的工作效率,但是高并发的场景下,多线程会存在线程安全问题,用锁来解决。

    内容一览:

    • 分析同步问题的核心三要素

    • 锁的分类

    • Netty 玩转锁的五个关键点

      • 在意锁的对象和范围 -> 减少粒度

      • 注意锁的对象本身大小 -> 减少空间占用

      • 注意锁的速度 -> 提高速度

      • 不同场景选择不同的并发类 -> 因需而变

      • 衡量好锁的价值 -> 能不用就不用

    1、分析同步问题的核心三要素

    • 原子性:“并无一气呵成,岂能无懈可击”,如:高并发下的 i++

    • 可见性:“你做的改变,别人看不见”

    • 有序性:“不按套路出牌”

    2、锁的分类

    • 竞争的态度:乐观锁(java.util.concurrent 包中的原子类) 与 悲观锁(synchronized)

    • 等待锁的人是否公平:公平锁 new ReentrantLock(true) 与 非公平锁 new ReentrantLock()

    • 是否可共享:共享锁 与 独享锁 - ReadWriteLock,其读锁是共享锁,其写锁是独享锁

    3、Netty 玩转锁的五个关键点

    3.1、在意锁的对象和范围 -> 减少粒度

    例:初始化 channel (io.netty.bootstrap.ServerBootstrap#init)

    Synchronized method -> Synchronized block

    
        void init(Channel channel) throws Exception {
            Map<ChannelOption<?>, Object> options = this.options0();
            synchronized(options) {
                setChannelOptions(channel, options, logger);
            }
    
            Map<AttributeKey<?>, Object> attrs = this.attrs0();
            synchronized(attrs) {
                Iterator var5 = attrs.entrySet().iterator();
    
                while(true) {
                    if (!var5.hasNext()) {
                        break;
                    }
    
                    Entry<AttributeKey<?>, Object> e = (Entry)var5.next();
                    AttributeKey<Object> key = (AttributeKey)e.getKey();
                    channel.attr(key).set(e.getValue());
                }
            }
            //...
        }
    

    3.2、注意锁的对象本身大小 -> 减少空间占用

    例:统计待发送的字节数 (io.netty.channel.ChannelOutboundBuffer)

    AtomicLong -> Volatile long + AtomicLongFieldUpdater

    AtomicLong vs long

    前者是一个对象,包含对象头(object header)以用来保存 hashcode、lock等信息,32 位系统占用 8 字节,64 位系统占用 16 字节,所以在 64 位系统下:

    • volatile long = 8 bytes
    • AtomicLong = 8 bytes (volatile long) + 16 bytes (object header) + 8 bytes (引用) = 32 bytes

    至少节约 24 字节!

    结论:
    Atomic* objects -> Volatile primary type + static Atomic*FieldUpdater

    原子类型的对象可以用 volatile 修饰的基础类型来代替,以节省空间

    3.3、注意锁的速度 -> 提高速度(提高并发性)

    例1:记录内存分配字节数等功能用到的 LongCounter (io.netty.util.internal.PlatformDependent#newLongCounter())

    高并发时:java.util.concurrent.atomic.AtomicLong -> java.util.concurrent.atomic.LongAdder (JDK)

    结论:及时衡量,使用jdk最新功能

    例2:曾经根据不同情况,选择不同的并发包实现:JDK < 1.8 考虑 ConcurrentHashMapV8(ConcurrentHashMap 在 JDK8 中的版本)

    3.4、不同场景选择不同的并发类 -> 因需而变

    例1:关闭和等待关闭事件执行器(Event Executor):

    Object.wait/notify -> CountDownLatch

    io.netty.util.concurrent.SingleThreadEventExecutor#threadLock

    例2:Nio Event Loop 中负责存储 task 的 Queue

    Jdk's LinkedBlockingQueue(MPMC) -> jctools' MPSC

    MPMC: muti-producer & muti-consumer
    MPSC: muti-producer & simple-consumer

    io.netty.util.internal.PlatformDependent.Mpsc#newMpscQueue(int)

    3.5、衡量好锁的价值 -> 能不用就不用

    生活场景:

    饭店提供了很多包厢,服务模式:

    • 一个服务员固定服务某几个包厢模式;

    • 所有服务员服务所有的包厢模式;

    表面上看,前者效率没有后者高,但实际上它避免了服务员间的沟通(上下文切换)等开销,避免客人与服务员之间导出乱串,管理简单。

    局部串行:Channel 的 I/O 请求处理 Pipeline 是串行的

    整体并行:多个串行化线程(Nio Event Loop)

    Netty 应用场景下:局部串行 + 整体并行 > 一个队列 + 多个线程模式:

    • 降低用户开发难度、逻辑简单、提升处理性能

    • 避免锁带来的上下文切换和并发保护等额外开销

    本文由博客一文多发平台 OpenWrite 发布!

    相关文章

      网友评论

          本文标题:boy-learning-netty | 11 Netty 的哪

          本文链接:https://www.haomeiwen.com/subject/jvetbctx.html