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

net.e6tech.elements.common.util.concurrent.ThreadLocalMap Maven / Gradle / Ivy

There is a newer version: 2.7.9
Show newest version
/*
 * Copyright 2015-2022 Futeh Kao
 *
 * 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 net.e6tech.elements.common.util.concurrent;

import java.util.*;

public class ThreadLocalMap implements Map {
    private final ThreadLocal> threadLocal = new ThreadLocal<>();

    private final ThreadLocal lastUpdate = new ThreadLocal<>();

    private Map map;

    private volatile Object dirty = new Object();

    public ThreadLocalMap() {
        map = new LinkedHashMap<>();
        threadLocal.set(map);
        lastUpdate.set(dirty);
    }

    public ThreadLocalMap(int initialCapacity) {
        map = new LinkedHashMap<>(initialCapacity);
        threadLocal.set(map);
        lastUpdate.set(dirty);
    }

    public ThreadLocalMap(Map map) {
        this.map = map;
        threadLocal.set(map);
        lastUpdate.set(dirty);
    }

    public Object lastUpdate() {
        return lastUpdate.get();
    }

    public Map merge() {
        return merge(dirty);
    }

    Map merge(Object dirt) {
        Map local = threadLocal.get();
        if (local == map) {
            lastUpdate.set(dirt);
            return map;
        }

        if (dirt == lastUpdate.get() && local != null ) {
            return local;
        }

        int localSize = local != null ? local.size() : 0;
        Map combined = new LinkedHashMap<>(localSize + map.size() + 1);
        combined.putAll(map);
        if (local != null)
            combined.putAll(local);
        lastUpdate.set(dirt);
        threadLocal.set(combined);
        return combined;
    }

    public synchronized Map mapThreadLocal() {
        if (threadLocal.get() == null)
            threadLocal.set(new LinkedHashMap<>());
        return threadLocal.get();
    }

    public boolean isDirty() {
        return dirty != lastUpdate.get();
    }

    @Override
    public synchronized int size() {
        return merge().size();
    }

    @Override
    public synchronized boolean isEmpty() {
        Map m = threadLocal.get();
        if (m != null && !m.isEmpty()) {
            return false;
        } else {
            synchronized (this) {
                return map.isEmpty();
            }
        }
    }

    @Override
    public boolean containsKey(Object key) {
        Map m = threadLocal.get();
        if (m != null && m.containsKey(key)) {
            return true;
        } else {
            synchronized (this) {
                return map.containsKey(key);
            }
        }
    }

    @Override
    public synchronized boolean containsValue(Object value) {
        Map m = threadLocal.get();
        if (m != null && m.containsValue(value)) {
            return true;
        } else {
            synchronized (this) {
                return map.containsValue(value);
            }
        }
    }

    @Override
    public V get(Object key) {
        Map m = threadLocal.get();
        if (m != null && m.containsKey(key)) {
            return m.get(key);
        } else {
            synchronized (this) {
                return map.get(key);
            }
        }
    }

    @Override
    public V put(K key, V value) {
        V old;
        Map m = threadLocal.get();
        if (m == map)
            return syncPut(key, value);

        if (m != null) {
            if (m.containsKey(key)) {
                old = m.put(key, value);
                syncPut(key, value);
            } else {
                m.put(key, value);
                old = syncPut(key, value);
            }
        } else {
            m = new LinkedHashMap<>();
            threadLocal.set(m);
            m.put(key, value);
            old = syncPut(key, value);
        }
        return old;
    }

    private synchronized V syncPut(K key, V value) {
        boolean merged = dirty == lastUpdate.get();
        dirty = new Object();
        if (merged) {
            lastUpdate.set(dirty);  // current thread is updated to up to date.  However, other threads are not
        }

        return map.put(key, value);
    }

    public V putThreadLocal(K key, V value) {
        V old;
        Map m = threadLocal.get();
        if (m != null) {
            old = m.put(key, value);
        } else {
            m = new LinkedHashMap<>();
            threadLocal.set(m);
            old = m.put(key, value);
        }
        return old;
    }

    @Override
    public synchronized V remove(Object key) {
        dirty = new Object();
        Map m = threadLocal.get();
        if (m == map)
            return m.remove(key);

        if (m != null) {
            map.remove(key);
            return m.remove(key);
        } else {
            return map.remove(key);
        }
    }

    public V removeThreadLocal(K key) {
        Map m = threadLocal.get();
        if (m != null) {
            return m.remove(key);
        } else {
            return null;
        }
    }

    @Override
    public synchronized void putAll(Map m) {
        dirty = new Object();
        Map local = threadLocal.get();
        if (local == map) {
            local.putAll(m);
            return;
        }

        if (local != null)
            local.putAll(m);
        map.putAll(m);
    }

    @Override
    public synchronized void clear() {
        dirty = new Object();
        Map local = threadLocal.get();
        if (local != map) {
            threadLocal.remove();
        }
        lastUpdate.set(dirty);
        map.clear();
    }

    @Override
    public synchronized Set keySet() {
        return merge().keySet();
    }

    @Override
    public synchronized Collection values() {
        return merge().values();
    }

    @Override
    public Set> entrySet() {
        Map m = threadLocal.get();
        if (m == map) {
           return map.entrySet();
        }

        Map> entries = new LinkedHashMap<>(2 * map.size() + 1);
        if (m != null) {
            for (Entry e : m.entrySet())
                entries.put(e.getKey(), new TLEntry(e, null));
        }

        synchronized (this) {
            for (Entry e : map.entrySet()) {
                TLEntry entry = entries.get(e.getKey());
                if (entry != null)
                    entry.global = e;
                else
                    entries.put(e.getKey(), new TLEntry(null, e));
            }
        }
        return new HashSet<>(entries.values());
    }

    public synchronized void clearThreadLocal() {
        dirty = new Object();
        Map local = threadLocal.get();
        if (local == map) {
            clear();
            return;
        }

        threadLocal.remove();
        lastUpdate.set(dirty);
    }

    class TLEntry implements Entry {
        Entry local;
        Entry global;

        TLEntry(Entry local, Entry global) {
            this.local = local;
            this.global = global;
        }

        @Override
        public K getKey() {
            if (local != null)
                return local.getKey();
            return global.getKey();
        }

        @Override
        public V getValue() {
            if (local != null)
                return local.getValue();
            return global.getValue();
        }

        @Override
        public V setValue(V value) {
            dirty = new Object();
            V old = null;
            if (local != null) {
                old = local.setValue(value);
                if (global != null)
                    global.setValue(value);
            } else if (global != null) {
                old = global.setValue(value);
            }
            return old;
        }

        @Override
        public boolean equals(Object o) {
            if (local != null) {
                return local.equals(o);
            } else if (global != null) {
                return global.equals(o);
            }
            return false;
        }

        @Override
        public int hashCode() {
            if (local != null) {
                return local.hashCode();
            } else if (global != null) {
                return global.hashCode();
            }
            return 0;
        }
    }
}