Netty4学习笔记(7)-- AttributeMap

IoSession

MINA的IoSession接口定义了一组方法,让我们可以利用IoSession来存储一些数据:

public interface IoSession {
    getAttribute(Object key)
    getAttribute(Object key, Object defaultValue)
    setAttribute(Object key)
    setAttribute(Object key, Object value)
    setAttributeIfAbsent(Object key)
    setAttributeIfAbsent(Object key, Object value)
    replaceAttribute(Object key, Object oldValue, Object newValue)
    removeAttribute(Object key)
    removeAttribute(Object key, Object value)
    containsAttribute(Object key)
    getAttributeKeys() 
}

AttributeMap接口

Netty将这种看似Map的功能进一步抽象,形成了 AttributeMap接口:

public interface AttributeMap {
     Attribute attr(AttributeKey key);
}

AttributeMap接口只有一个attr()方法,接收一个AttributeKey类型的key,返回一个Attribute类型的value。按照Javadoc,AttributeMap实现必须是线程安全的。AttributeMap内部结构看起来像下面这样:

Netty4学习笔记(7)-- AttributeMap_第1张图片


谁实现了AttributeMap接口?

答案是所有的Channel和ChannelHandlerContext,如下面的类图所示:

Netty4学习笔记(7)-- AttributeMap_第2张图片


AttributeKey

AttributeKey有两个地方值得 一提。第一是AttributeKey是个泛型类,在我看来,这也是Netty相对于MINA的一处改进。在使用IoSession的时候,你必须进行强制类型转换:

int userId = (Integer) ioSession.getAttribute("userId");
但是使用AttributeMap却不需要:

AttributeKey KEY_USER_ID = AttributeKey.valueOf("userId");
int userId = channel.attr(KEY_USER_ID).get();

第二是AttributeKey继承了 UniqueName类,也就是说,对于每一个name,应该只有唯一一个AttributeKey与之对应。这一点看起来很奇怪,但是当知道 DefaultAttributeMap内部使用了 IdentityHashMap的时候,就觉得合情合理。下面是与AttributeKey相关的类图,至于UniqueName如何保证name唯一,稍后介绍:

Netty4学习笔记(7)-- AttributeMap_第3张图片


UniqueName

UniqueName实际上是靠传入构造函数的一个map来保证name的唯一性:

@Deprecated
public class UniqueName implements Comparable {

    private static final AtomicInteger nextId = new AtomicInteger();

    private final int id;
    private final String name;

    public UniqueName(ConcurrentMap map, String name, Object... args) {
        if (map == null) {
            throw new NullPointerException("map");
        }
        if (name == null) {
            throw new NullPointerException("name");
        }
        if (args != null && args.length > 0) {
            validateArgs(args);
        }

        if (map.putIfAbsent(name, Boolean.TRUE) != null) {
            throw new IllegalArgumentException(String.format("'%s' is already in use", name));
        }

        id = nextId.incrementAndGet();
        this.name = name;
    }
    ...
}
但是Javadoc说这个类存在跟类加载器相关的问题,所以被废弃了。AttributeKey继承了UniqueName,内部使用ConcurrentHashMap来保证name的唯一性:

public final class AttributeKey extends UniqueName {

    private static final ConcurrentMap names = PlatformDependent.newConcurrentHashMap();

    @SuppressWarnings("deprecation")
    public static  AttributeKey valueOf(String name) {
        return new AttributeKey(name);
    }

    @Deprecated
    public AttributeKey(String name) {
        super(names, name);
    }
}

Attribute接口

Attribute接口除了有必须的get()set()remove()方法外,还有几个原子方法:

public interface Attribute {
    AttributeKey key();
    T get();
    void set(T value);
    T getAndSet(T value);
    T setIfAbsent(T value);
    T getAndRemove();
    boolean compareAndSet(T oldValue, T newValue);
    void remove();
}

DefaultAttributeMap

如前面的类图所示,DefaultAttributeMap实现了AttributeMap接口,AbstractChannelDefaultChannelHandlerContext通过继承DefaultAttributeMap也实现了AttributeMap接口。下面是DefaultAttributeMap的部分代码:

public class DefaultAttributeMap implements AttributeMap {

    @SuppressWarnings("rawtypes")
    private static final AtomicReferenceFieldUpdater updater =
            AtomicReferenceFieldUpdater.newUpdater(DefaultAttributeMap.class, Map.class, "map");

    // Initialize lazily to reduce memory consumption; updated by AtomicReferenceFieldUpdater above.
    @SuppressWarnings("UnusedDeclaration")
    private volatile Map, Attribute> map;

    @Override
    public  Attribute attr(AttributeKey key) {
        Map, Attribute> map = this.map;
        if (map == null) {
            // Not using ConcurrentHashMap due to high memory consumption.
            map = new IdentityHashMap, Attribute>(2);
            if (!updater.compareAndSet(this, null, map)) {
                map = this.map;
            }
        }

        synchronized (map) {
            @SuppressWarnings("unchecked")
            Attribute attr = (Attribute) map.get(key);
            if (attr == null) {
                attr = new DefaultAttribute(map, key);
                map.put(key, attr);
            }
            return attr;
        }
    }
    ...
}
可以看到:

  1. map是延迟创建的(为了减少内存消耗),更准确的说,map在attr()方法第一次被调用的时候创建
  2. map被声明为volatile,再加上AtomicReferenceFieldUpdater.compareAndSet()方法的使用,map的null判断和赋值这段代码可以不使用synchronized
  3. 内部使用的是IdentityHashMap,所以AttributeKey必须是唯一的,因为IdentityHashMap使用==而不是equals()方法来判断两个key是否相同
  4. attr()方法被调用时,如果key还没有关联attribute,会自动创建一个


DefaultAttribute

最后,DefaultAttribute通过继承AtomicReference获得了原子操作能力:

public class DefaultAttributeMap implements AttributeMap {
    private static final class DefaultAttribute extends AtomicReference implements Attribute {

        private static final long serialVersionUID = -2661411462200283011L;

        private final Map, Attribute> map;
        private final AttributeKey key;

        DefaultAttribute(Map, Attribute> map, AttributeKey key) {
            this.map = map;
            this.key = key;
        }

        @Override
        public AttributeKey key() {
            return key;
        }

        @Override
        public T setIfAbsent(T value) {
            while (!compareAndSet(null, value)) {
                T old = get();
                if (old != null) {
                    return old;
                }
            }
            return null;
        }

        @Override
        public T getAndRemove() {
            T oldValue = getAndSet(null);
            remove0();
            return oldValue;
        }

        @Override
        public void remove() {
            set(null);
            remove0();
        }

        private void remove0() {
            synchronized (map) {
                map.remove(key);
            }
        }
    }
}
下面是DefaultAttributeMap的内部结构:

Netty4学习笔记(7)-- AttributeMap_第4张图片

结论

  • ChannelChannelHandlerContext都扩展了AttributeMap接口,因此每一个Channel和ChannelHandlerContext实例都可以像Map一样按照key来存取value
  • AttributeMap实现必须是线程安全的,因此,attr()方法可以在任何线程里安全的调用
  • AttributeKey必须是唯一的,因此最好定义成全局变量(比如static final类型)
  • 默认的Attribute实现继承自AtomicReference,因此也是线程安全的


你可能感兴趣的:(Java,NIO,Netty)