All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.zeno.flatblob.FlatBlobTypeCache Maven / Gradle / Ivy

/*
 *
 *  Copyright 2014 Netflix, Inc.
 *
 *     Licensed under the Apache License, Version 2.0 (the "License");
 *     you may not use this file except in compliance with the License.
 *     You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 *
 */
package com.netflix.zeno.flatblob;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public class FlatBlobTypeCache {

    private final String name;
    private final ConcurrentHashMap references;
    private final ConcurrentHashMap ordinalLookup;

    public FlatBlobTypeCache(String name) {
        this.name = name;
        this.references = new ConcurrentHashMap();
        this.ordinalLookup = new ConcurrentHashMap();
    }

    public String getName() {
        return name;
    }

    @SuppressWarnings("unchecked")
    public T putIfAbsent(int ordinal, T obj) {
        if(ordinal >= 0) {
            Integer ordinalInteger = Integer.valueOf(ordinal);
            /// create a new key
            ObjectIdentityKey key = new ObjectIdentityKey(obj);
            while(true) {
                /// try to put the key in the references map.
                ObjectIdentityKey existingKey = references.putIfAbsent(ordinalInteger, key);
                if(existingKey == null) {
                    ordinalLookup.put(key, ordinalInteger);
                    return obj;
                }

                /// if unsuccessful, try to increment the references for the key which won the race
                if(existingKey.tryIncrementReferences())
                    return (T) existingKey.getObject();

                /// use the older object in the cache.
                key.setObject(existingKey.getObject());

                /// this will spin, but not acquire the lock, thus preventing starvation
                while(references.get(ordinalInteger) == existingKey);
            }
        }
        return obj;
    }

    public void evict(T obj) {
        ObjectIdentityKey lookupKey = getLookupKey(obj);
        if(lookupKey != null) {
            Integer ordinalInteger = ordinalLookup.get(lookupKey);
            ObjectIdentityKey actualKey = references.get(ordinalInteger);

            if(actualKey.decrementReferences()) {
                ordinalLookup.remove(actualKey);
                references.remove(ordinalInteger);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public T get(int ordinal) {
        if(ordinal < 0)
            return null;
        ObjectIdentityKey identityKey = references.get(Integer.valueOf(ordinal));
        if(identityKey != null && identityKey.tryIncrementReferences())
            return (T) identityKey.getObject();
        return null;
    }

    /// cache lookup keys to reduce object allocation.
    private static ThreadLocal lookupKey = new ThreadLocal();

    private ObjectIdentityKey getLookupKey(Object obj) {
        ObjectIdentityKey key = lookupKey.get();
        if(key == null) {
            key = new ObjectIdentityKey();
            lookupKey.set(key);
        }

        key.setObject(obj);

        return key;
    }

    private static class ObjectIdentityKey {
        private Object obj;
        private final AtomicInteger referenceCount;

        public ObjectIdentityKey() {
            this.referenceCount = new AtomicInteger(0);
        }

        public ObjectIdentityKey(Object obj) {
            this.obj = obj;
            this.referenceCount = new AtomicInteger(1);
        }

        public Object getObject() {
            return obj;
        }

        public void setObject(Object obj) {
            this.obj = obj;
        }

        /**
         * We will only increment references if the number of references does not equal 0.
         *
         * If the number of references reaches 0, then this entry will be scheduled for eviction.
         *
         * @return
         */
        public boolean tryIncrementReferences() {
            while(true) {
                int current = referenceCount.get();
                if(current == 0)
                    return false;
                int next = current + 1;
                if (referenceCount.compareAndSet(current, next))
                    return true;
            }
        }

        /**
         * Decrement references, and return true if the number of references reaches 0.
         *
         * @return
         */
        public boolean decrementReferences() {
            return referenceCount.decrementAndGet() == 0;
        }

        public int hashCode() {
            return System.identityHashCode(obj);
        }

        public boolean equals(Object other) {
            if(other instanceof ObjectIdentityKey) {
                return obj == ((ObjectIdentityKey)other).getObject();
            }
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy