关于java:理解Java-ThreadLocal

33次阅读

共计 5003 个字符,预计需要花费 13 分钟才能阅读完成。

原文链接 了解 Java ThreadLocal

ThreadLocal 是 Java 提供的为每个线程存储线程独立的数据的存储形式,也就是说用 ThreadLocal 来保留的数据,只能被以后线程所拜访,其余线程无法访问,因为只有(一个线程)以后线程可能拜访,所以它是线程平安的,能够用来存储一些不能被共享的数据。

根本应用办法

ThreadLocal 应用起来十分的简略,它反对泛型,能够把任意类型的数据放进 ThreadLocal,一个 ThreadLocal 对象只能放一个对象:

ThreadLocal<String> mLocalCEOHolder = new ThreadLocal<>();
ThreadLocal<Integer> mOrdersCountHolder = new ThreadLocal<>();

mLocalCEOHolder.set("Alex Hilton");

String ceo = mLocalCEOHolder.get();

mOrdersCountHolder.set(30249);

int order = mOrdersCountHolder.get();

实现原理解析

就按下面的例子来解析它的实现原理:

  • 创立 ThreadLocal 对象

    先来看看它的构造方法:

/**
 * Creates a thread local variable.
 * @see #withInitial(java.util.function.Supplier)
 */
public ThreadLocal() {}

很可怜,它的构造方法是空的,啥也没干。

  • set 办法
    再来看下它的 set 办法:
/**
 * Sets the current thread's copy of this thread-local variable
 * to the specified value.  Most subclasses will have no need to
 * override this method, relying solely on the {@link #initialValue}
 * method to set the values of thread-locals.
 *
 * @param value the value to be stored in the current thread's copy of
 *        this thread-local.
 */
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 的货色,如果它不为空就把以后对象 this(ThreadLocal 对象)作为 key,把要寄存的值作为 value,放到这个 ThreadLocalMap 外面,如果 map 为空就先创立再寄存。由此能够猜出 ThreadLocalMap 是一个 Map 型的数据结构,接着钻研 getMap 和 createMap,前面再具体说 ThreadLocalMap。

/**
 * Get the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param  t the current thread
 * @return the map
 */
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}


/**
 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
 */
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

getMap 比较简单,它返回 Thread 对象的域对象 threadLocal。createMap 也很简略创立一个 ThreadLocalMap 对象,而后把它赋值给 Thread 对象的域变量。

  • get 办法
    再来看看 get 办法:
/**
 * Returns the value in the current thread's copy of this
 * thread-local variable.  If the variable has no value for the
 * current thread, it is first initialized to the value returned
 * by an invocation of the {@link #initialValue} method.
 *
 * @return the current thread's value of this thread-local
 */
public T get() {Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {@SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
         }
    }
    return setInitialValue();}

/**
 * Variant of set() to establish initialValue. Used instead
 * of set() in case user has overridden the set() method.
 *
 * @return the initial value
 */
private T setInitialValue() {T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

get 办法与 set 办法相似,同样是从以后线程取其 map,从其中以以后 TheadLocal 对象为 key 来查找值,如果找到了,就返回。如果 map 为空,或者没找到怎么办。就用 setInitialValue 来初始化线程的 map 对象,这个与 set 办法是一样的,只不过用空值(null)。

  • ThreadLocalMap 对象
/**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
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;
        }
    }

    /**
     * The initial capacity -- MUST be a power of two.
     */
    private static final int INITIAL_CAPACITY = 16;

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;
        
    /* other codes ... */
}

残缺的就不贴了,大家能够本人去查看。简略来了解,其实它就是一个 HashMap,key 是对 ThreadLocal 对象的 WeakReference,value 是咱们放入 ThreadLocal 的对象。

到这里能够总结一下 ThreadLocal 的原理了:数据结构是存储在线程对象里的一个 Map 对象中,key 是 ThreadLocal 对象的 WeakReference,值就是咱们想要寄存的对象。

留神:上面提到的 Map 的意思是 Thread#threadLocals,也就是 ThreadLocalMap 对象

外围所在

网下面很多对于 ThreadLocal 的文章(如这个和这个)都没有讲清楚,到底它是用什么办法来保障只有以后线程能力拜访,它们说的是都是它的数据结构,这个下面曾经说了。然而光有一个 Map,就够了吗?Thread 对象有一个 Map 用来存储 ThreadLocal 数据,然而如果 Thread 有公开获取此 Map 的办法,那跟咱们应用的共享变量有什么区别?

ThreadLocal 的真正外围在于它取的 以后线程 的 Map:

  • 每次从 ThreadLocal 取数据也好,放数据也好,指标的 Map 都是以后的线程的 Map
  • 线程的 Map 是包拜访权限
  • 放数据也好,取数据也好都是从以后线程的 Map 里存和取

所以,ThreadLocal 最要害的就是由 Thread.currentThread()来保障以后线程的。线程到底是什么? 线程简略来了解就是一个 run 办法,或者说一堆办法调用,它是一个时序的概念,是一堆按某种程序运行的指令的汇合。所以,当你调用 ThreadLocal#set 或者 ThreadLocal#get 时,在 set 和 get 办法实现外面会调用 Thread.currentThread 来获得调用栈所在的线程 — 也就是以后线程,这也就保障了,一个线程只能获取本人的 Map。

另外,Map 必须得与每个线程对象绑定,但又因为这个域是 package 作用域,只有同一个 package 的能力拜访,所以只能从 ThreadLocal 里操作此 Map 也是绝对平安的,也就是说想操作此 Map 只能通过 ThreadLocal。当然了,如果能 Hack 一下,生成一个与 Thread 在同一个 package 的对象,就可能间接操作 Map,也就能突破 ThreadLocal 的封装了,这时 Map 就变成可共享的了,也就失去了线程独有的个性。

典型利用

最典型的利用要数 Looper 类的实现。

Looper 的作用是帮忙线程创立并运行一个音讯循环(MessageQueue),很显然,一个线程有且只能有一个,那么 ThreadLocal 是最佳的计划。

Android 当中的实现

与规范的 Java 的实现原理是一样的,都是把 Map 当作 Thread 的一个域,package 作用域,ThreadLocal 作为 key, 外面的值作 value。

只不过 Map 的具体实现略有不同。

原创不易,打赏 点赞 在看 珍藏 分享 总要有一个吧

正文完
 0