ThreadLocal类设计思路、使用场景与源码详解

2017-11-16  本文已影响7人  AndroidTony

导论

如果让你设计一个变量,在不同线程的可以直接获取使用,不需要同步操作,读写都不影响其它线程的读写,你会怎么设计?

1.0版

1.0版可能会是这样,在Thread类中定义一个字段Object threadField,以及定义一个get与set操作
这样在需要使用的时候,调用Thread.currentThread().setThreadField(obj)进行设置,Thread.currentThread().getThreadField()进行读取,即可以实现这个功能。
但是,这样设计有问题,第一,存放一个数据就要添加一个字段,显然不科学,至少应该可以存放多个数据,所以大概需要用Hashmap来存放数据。第二,数据的类型不确定,导致使用的时候,还需要向下转型,不方便,所以应该用泛型来解决这个问题。

2.0版

在Thread类中定义字段HashMap <?,object>,面临一个问题,Key值的类型是什么?这个时候大概能想到,需要设计一个Class,作为Key值,方便用户通过Key去获取存放的数据。暂且称之为ThreadFieldKey。
数据类型不确定,需要使用泛型,泛型在哪定义?很容易想到,应该在ThreadFieldKey中,定义为ThreadFieldKey<T>.而getValue()的操作最终要返回T类型,Thread类又不持有T,所以getValue应该放在ThreadFieldKey<T>类中。 至此可以得到以下代码:

class Thread{
    HashMap<ThreadFieldKey,object> threadFieldHashMap;
    public HashMap<ThreadFieldKey,object> getThreadFieldHashMap(){
    return threadFieldHashMap;
}
}

class ThreadFieldKey<T>{

 public void setValue(T value){
   getMap.put(this,value);
}

public void getMap(){
   Thread t = Thread.currentThread();
   HashMap<ThreadFieldKey<T>,T>  map = t.getThreadFieldHashMap();
   return map;
}

public void getValue(T value){
    return (T)getMap.get(this);
}
}

class Client{
   public void main(){
   ThreadFieldKey<Double> doubleKey = new ThreadFieldKey<Double>();
   doubleKey .put(***);
   Double d = doubleKey.get();
   ThreadFieldKey<Student> studentKey = new ThreadFieldKey<Student>();
   studentKey .put(***);
   Student s = studentKey .get();
}
}

至此就实现了我们想要的功能。我们来看jdk是怎么实现的

官网版

class Thread{
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

public class ThreadLocal<T> {
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
……
}

class Client{
        ThreadLocal<Double> threadLocal = new ThreadLocal<>();
        threadLocal.set(0.2);
        threadLocal.get();
}
}

可以看到,和2.0相比较,区别在于把HashMap替换为了ThreadLocal的静态内部类ThreadLocalMap,两者功能一样,都是存放键值对。ThreadLocalMap其它基本一样。

ThreadLocalMap.Entry类

重点讲一下这个Entry,对比一下

static class ThreadLocalMap {

static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
}
class WeakHashMap{
 
 private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;
}

两者的Entry都是继承与WeakReference,有什么用?显然,这是为了避免内存泄漏。
WeakReference标志性的特点是:只要没有其它地方引用该对象,则对象必然会在GC时被回收。
当用户不再使用,容器会一直持有该对象的引用,导致其无法回收,所以使用WeakReference。

ThreadLocalMap会在set,get以及resize等方法中对stale slots(根据reference.get() == null来判断,如果为true则表示过期,程序内部称为stale slots)做自动删除replaceStaleEntry(key, value, i)(set以及get不保证所有过期slots会在操作中会被删除,而resize则会删除threadLocalMap中所有的过期slots).当然将threadLocal对象设置为null并不能完全避免内存泄露对象,最安全的办法仍然是调用ThreadLocal的remove方法,来彻底避免可能的内存泄露。

rivate void set(ThreadLocal key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

使用场景

ThreadLocal中操作的对象都是当前线程的threadLocals,所以在不同线程读写,操作的是不同线程的变量,自然就没有共享访问的问题。

ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。

private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }
上一篇下一篇

猜你喜欢

热点阅读