1. 程式人生 > >Netty4學習筆記(7)-- AttributeMap

Netty4學習筆記(7)-- AttributeMap

IoSession

MINAIoSession介面定義了一組方法,讓我們可以利用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 {
    <T> Attribute<T> attr(AttributeKey<T> key);
}

AttributeMap介面只有一個attr()方法,接收一個AttributeKey型別的key,返回一個Attribute型別的value。按照Javadoc,AttributeMap實現必須是執行緒安全的。AttributeMap內部結構看起來像下面這樣:

誰實現了AttributeMap介面?

AttributeKey

AttributeKey有兩個地方值得 一提。第一是AttributeKey是個泛型類,在我看來,這也是Netty相對於MINA的一處改進。在使用IoSession的時候,你必須進行強制型別轉換:

int userId = (Integer) ioSession.getAttribute("userId");
但是使用AttributeMap卻不需要:
AttributeKey<Integer> KEY_USER_ID = AttributeKey.valueOf("userId");
int userId = channel.attr(KEY_USER_ID).get();

第二是AttributeKey繼承了UniqueName類,也就是說,對於每一個name,應該只有唯一一個AttributeKey與之對應。這一點看起來很奇怪,但是當知道DefaultAttributeMap內部使用了IdentityHashMap的時候,就覺得合情合理。下面是與AttributeKey相關的類圖,至於UniqueName如何保證name唯一,稍後介紹:


UniqueName

UniqueName實際上是靠傳入建構函式的一個map來保證name的唯一性:

@Deprecated
public class UniqueName implements Comparable<UniqueName> {

    private static final AtomicInteger nextId = new AtomicInteger();

    private final int id;
    private final String name;

    public UniqueName(ConcurrentMap<String, Boolean> 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<T> extends UniqueName {

    private static final ConcurrentMap<String, Boolean> names = PlatformDependent.newConcurrentHashMap();

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

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

Attribute介面

Attribute介面除了有必須的get()set()remove()方法外,還有幾個原子方法:

public interface Attribute<T> {
    AttributeKey<T> 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<DefaultAttributeMap, Map> updater =
            AtomicReferenceFieldUpdater.newUpdater(DefaultAttributeMap.class, Map.class, "map");

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

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

        synchronized (map) {
            @SuppressWarnings("unchecked")
            Attribute<T> attr = (Attribute<T>) map.get(key);
            if (attr == null) {
                attr = new DefaultAttribute<T>(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<T> extends AtomicReference<T> implements Attribute<T> {

        private static final long serialVersionUID = -2661411462200283011L;

        private final Map<AttributeKey<?>, Attribute<?>> map;
        private final AttributeKey<T> key;

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

        @Override
        public AttributeKey<T> 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的內部結構:

結論

  • ChannelChannelHandlerContext都擴充套件了AttributeMap介面,因此每一個Channel和ChannelHandlerContext例項都可以像Map一樣按照key來存取value
  • AttributeMap實現必須是執行緒安全的,因此,attr()方法可以在任何執行緒裡安全的呼叫
  • AttributeKey必須是唯一的,因此最好定義成全域性變數(比如static final型別)
  • 預設的Attribute實現繼承自AtomicReference,因此也是執行緒安全的