当前位置: 亚洲城ca88 > ca88 > 正文

ca88当地线程,ThreadLocal源码分析

时间:2019-10-11 05:26来源:ca88
ThreadLocal是贰个线程内部数据存款和储蓄类,通过她能够在内定的线程中积存数据。存款和储蓄后,只可以在钦命的线程中收获到存款和储蓄的数据,对别的线程来讲无法获取到多少。

ThreadLocal是贰个线程内部数据存款和储蓄类,通过她能够在内定的线程中积存数据。存款和储蓄后,只可以在钦命的线程中收获到存款和储蓄的数据,对别的线程来讲无法获取到多少。

第一通过难点去看源码

前言:

          相信读者在网络也看了比比较多有关ThreadLocal的资料,比比较多博客都这么说:ThreadLocal为消除二十四线程程序的产出难点提供了一种新的笔触;ThreadLocal的指标是为着消除三十二线程访问财富时的分享问题。如若您也这么感到,这今后给你10分钟,清空此前对ThreadLocal的失实认识!

平常使用意况相当的少,当某个数据是以线程为成效域况兼差别线程具备区别的数码别本的时候,能够虚拟选拔ThreadLocal。

  1. ThreadLocal通过空中换取线程变量安全的布道科学吧
  2. ThreadLocal为何说会存在内存泄漏
  3. ThreadLocal、ThreadLocalMap、Thread 三者之间的关联

一、什么是ThreadLocal?

        依照JDK源码中的注释翻译过来正是:ThreadLocal类用来提供线程内部的片段变量。这种变量在二十四线程处境下访谈(通过get也许set方法访谈)时能保障各类线程里的变量相对独立于其余线程内的变量。ThreadLocal实例平常来讲都是private static类型的,用于关联线程和线程的上下文。通俗地说:ThreadLocal为每三个线程维护了一份和睦的变量,在本人线程内传递,不受别的线程影响。

Android源码的Lopper、ActivityThread乃至AMS中都用到了ThreadLocal。

ThreadLocal源码

以下是抽出的ThreadLocal源码关键部分

public class ThreadLocal<T> {
    public ThreadLocal() {
    }
   public void set(T value) {
        Thread t = Thread.currentThread();
        //这个ThreadLocalMap 来源于Thread.currentThread,因此该Map是和Thread绑定的
        ThreadLocalMap map = getMap(t);
        //此处是Thread 中ThreadLocalMap的懒加载模式
        if (map != null)
            //这里设置的是ThreadLocal对象和value的映射
            map.set(this, value);
        else
            createMap(t, value);
    }

    public T get() {
        Thread t = Thread.currentThread();
         //这个ThreadLocalMap 来源于Thread.currentThread,因此该Map是和Thread绑定的
        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();
    }

    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;
    }
    protected T initialValue() {
        return null;
    }


   public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

      ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

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

    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

    static class ThreadLocalMap {
          //类似于Map的实现,里面存放的是ThreadLocal和Thread的键值映射

        //这个是弱引用实现的Entry 键值对对象类
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
     }
}

二、为何要用ThreadLocal?

        在当今的互连网高并发的一世,同四个伸手在一秒钟之内或许会发生成千上万次以致越来越多,如此之大的并发亮,服务器在维护变量的必须求思索线程安全主题材料,要是选用同步锁来保证一个变量在同期只可以被一个线程访问,那么在高并发量的条件下,服务器品质必然不可能达到规定的标准供给。此时就供给用到ThreadLocal来为每二个线程去维护独立的变量。

public class ThreadLocalActivity extends AppCompatActivity {private ThreadLocal<String> name = new ThreadLocal<>();@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread_local); name.set; Log.d("ThreadLocalActivity", "Thread:"   Thread.currentThread().getName()   " name:"   name.get; new Thread("thread1") { @Override public void run() { name.set; Log.d("ThreadLocalActivity", "Thread:"   Thread.currentThread().getName()   " name:"   name.get; } }.start(); new Thread("thread2") { @Override public void run() { Log.d("ThreadLocalActivity", "Thread:"   Thread.currentThread().getName()   " name:"   name.get; } }.start();}}

1. ThreadLocal透过空中换取线程变量安全的说法科学吧

上边能够见见ThreadLocal类提供最多少个重大的不二法门

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

先看下get方法的贯彻:

    public T get() {
        Thread t = Thread.currentThread();
         //这个ThreadLocalMap 来源于Thread.currentThread,因此该Map是和Thread绑定的
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //此处的key是this,看看this为key的有没有ThreadLocalMap.Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 如果没有查找到结果值,就设置并返回初始化值对象
        return setInitialValue();
    }

先是句是得到当前线程,然后经过getMap(t)方法取获得贰个map,map的项目为ThreadLocalMap。然后跟着上面获取到<key,value>键值对,注意这里得到键值对传进去的是 this,并不是时下线程t。
如果获得成功,则赶回value值。
假定map为空,则调用setInitialValue方法重回value。
上边包车型地铁每一句来精心深入分析:
先是看一下getMap方法中做了如何:

      ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

在getMap中,是调用当期线程t,重返当前线程t中的贰个分子变量threadLocals。
那么大家后续取Thread类中取看一下分子变量threadLocals是怎么:

public class Thread implements Runnable {
      ThreadLocal.ThreadLocalMap threadLocals = null;
}

实在正是二个ThreadLocalMap,那些项目是ThreadLocal类的一个之中类,大家继续取看ThreadLocalMap的完毕:

    static class ThreadLocalMap {
          //类似于Map的实现,里面存放的是ThreadLocal和Thread的键值映射

        //这个是弱引用实现的Entry 键值对对象类
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

能够看看ThreadLocalMap的Entry承继了WeakReference,何况动用ThreadLocal作为键值。
接下来再持续看setInitialValue方法的具体贯彻:

    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;
    }

很轻易了然,就是只要map不为空,就安装键值对,为空,再成立Map,看一下createMap的兑现:

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

ThreadLocal是如何是好到为每多个线程维护变量的别本的吗?至此,可能大部分爱人早已清楚了ThreadLocal是怎么着为各个线程成立变量的别本的。因为种种变量中都有一个别本所以能够说线程安全的。可是有个小标题,要是value是叁个引用类型的变量呢,那么这种状态下又将不是线程安全的。

ThreadLocal通过set(分享变量)然后再经过ThreadLocal方法get的是分享变量的引用!!! 即使多少个线程都在其施行进度中将共享变量参预到协调的ThreadLocal中,那正是各类线程都持有一份分享变量的引用别本,注意是引用别本,分享变量的实例只有一个。所以,ThreadLocal并未消除线程间分享变量的会见的事儿的。想要调节分享变量在七个线程之间依照程序猿想要的不二诀窍来进展,那是锁和线程间通讯的事,和ThreadLocal没有关系。事例仿效
总的看:每个线程中皆有二个谈得来的ThreadLocalMap类对象,能够将线程本人的对象保险到此中,各管各的,线程实施时期都得以正确的拜看见温馨的靶子。

三、ThreadLocal的用法

举二个榛子

      private static final ThreadLocal<Boolean> flag = new ThreadLocal<Boolean>()

{

@Override

protected Boolean initialValue()

{

return false;

}

};

在概念变量的时候提议重写开首化方法,设置变量早先值。

flag.set(true);

调用其set()方法设置变量值。

flag.get();

调用其get()方法过去变量别本的值。

flag.remove();

调用其remove()方法,清空变量别本,制止多线程访谈导致逻辑错误。

运营结果:

2. ThreadLocal为啥说会存在内部存储器泄漏

ThreadLocalMap使用ThreadLocal的弱引用作为key,假如贰个ThreadLocal未有外界强引用来援用它,那么系统 GC 的时候,这一个ThreadLocal势必会被回收,那样一来,ThreadLocalMap中就能够冒出key为null的Entry,就从不办法访谈这么些key为null的Entry的value,要是当前线程再缓缓不甘休以来,那一个key为null的Entry的value就能够直接存在一条强援引链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value长久不也许回收,造成内部存款和储蓄器泄漏。
实际,ThreadLocalMap的陈设中早就考虑到这种情景,也增进了一些防患措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里全体key为null的value。

但是这个被动的防备措施并没办法担保不会内部存款和储蓄器泄漏:

  • 行使static的ThreadLocal,延长了ThreadLocal的生命周期,恐怕导致的内部存款和储蓄器泄漏(参谋ThreadLocal 内部存款和储蓄器败露的实例分析)。
  • 分红使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就能够促成内部存款和储蓄器泄漏。

四、ThreadLocal的基本原理

ThreadLocal源码解析:

initialValue函数用来安装ThreadLocal的开首值

protected T initialValue() {

        return null;

    }

ThreadLocal的get方法:

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();

    }

依据最近线程获取到近来线程维护的map,然后从map中获取相应的变量值,倘使为空就调用设置最初值方法进行最初化。

ThreadLocal的setInitialValue方法:

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;

    }

setInitialValue()方法,先调用先河化方法赢得变量的始发值,然后存入map中,假诺map为空直接制造一个map,并将变量和其发轫值保存。

ThreadLocal的createMap方法:

void createMap(Thread t, T firstValue) {

        t.threadLocals = new ThreadLocalMap(this, firstValue);

    }

始建map方法很轻松,在那不多解释。

ThreadLocal的set方法:

public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

    }

同get()方法同样先获得map,然后将变量的值举办封存,若是map为空就调用上边的createMap(t, value)方法。

ThreadLocal的remove()方法:

public void remove() {

        ThreadLocalMap m = getMap(Thread.currentThread());

        if (m != null)

            m.remove(this);

    }

先获得map,然后将key所对应的键值对移除。

ThreadLocalMap是运用ThreadLocal的弱援用作为Key的二个map,差相当的少源码如下:

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;

            }

        }

...

...

}

ThreadLocalMap是Thread类维护的八个map。key是ThreadLocal变量,value是值

以下是引用关系图,实线表示强援用,虚线表示弱援用:

ca88 1

如上海教室,ThreadLocalMap使用ThreadLocal的弱引用作为key,如若三个ThreadLocal未有外界强引用引用他,那么系统gc的时候,那么些ThreadLocal势必会被回收,那样一来,ThreadLocalMap中就能出现key为null的Entry,可是这年无需担忧会产生内部存款和储蓄器泄漏,在JDK的ThreadLocalMap的统一计划中曾经思考到这种情景,也增进了有的防护方法,上面是ThreadLocalMap的getEntry方法的源码:

private Entry getEntry(ThreadLocal<?> key) {

            int i = key.threadLocalHashCode & (table.length - 1);

            Entry e = table[i];

            if (e != null && e.get() == key)

                return e;

            else

                return getEntryAfterMiss(key, i, e);

        }

getEntryAfterMiss函数的源码:

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {

            Entry[] tab = table;

            int len = tab.length;

            while (e != null) {

                ThreadLocal<?> k = e.get();

                if (k == key)

                    return e;

                if (k == null)

                    expungeStaleEntry(i);

                else

                    i = nextIndex(i, len);

                e = tab[i];

            }

            return null;

        }

expungeStaleEntry函数的源码:

private int expungeStaleEntry(int staleSlot) {

            Entry[] tab = table;

            int len = tab.length;

            // expunge entry at staleSlot

            tab[staleSlot].value = null;

            tab[staleSlot] = null;

            size--;

            // Rehash until we encounter null

            Entry e;

            int i;

            for (i = nextIndex(staleSlot, len);

                (e = tab[i]) != null;

                i = nextIndex(i, len)) {

                ThreadLocal<?> k = e.get();

                if (k == null) {

                    e.value = null;

                    tab[i] = null;

                    size--;

                } else {

                    int h = k.threadLocalHashCode & (len - 1);

                    if (h != i) {

                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until

                        // null because multiple entries could have been stale.

                        while (tab[h] != null)

                            h = nextIndex(h, len);

                        tab[h] = e;

                    }

                }

            }

            return i;

        }

收拾一下ThreadLocalMap的getEntry函数的流水生产线:

  1. 率先从ThreadLocal的第一手索引位置(通过ThreadLocal.threadLocalHashCode & (len-1)运算得到)获取Entry e,若是e不为null而且key一样则再次来到e;

2. 假使e为null也许key不雷同则向下贰个地点查询,假使下多个岗位的key和当下亟待查询的key相等,则赶回对应的Entry,不然,借使key值为null,则擦除该职位的Entry,不然继续向下三个地点查询。

      在此个进程中碰到的key为null的Entry都会被擦除,那么Entry内的value也就从未有过强援引链,自然会被回收,所以并不会时有发生内部存款和储蓄器溢出,不过为了程序作用乃至安全性,大家建议最权威动点用remove()方法去放活内部存款和储蓄器。

D/ThreadLocalActivity: Thread:main name:小明 D/ThreadLocalActivity: Thread:thread1 name:小红 D/ThreadLocalActivity: Thread:thread2 name:null

怎么选择弱援用

从表面上看内存泄漏的来源于在于运用了弱援引。英特网的篇章大都珍视剖判ThreadLocal使用了弱援用会招致内部存款和储蓄器泄漏,然则另八个主题素材也同样值得思索:为何选择弱援用并非强引用?

上面我们分二种景况商量:

  • key 使用强援引:援用的ThreadLocal的目的被回收了,可是ThreadLocalMap还持有ThreadLocal的强引用,若无手动删除,ThreadLocal不会被回收,导致Entry内部存款和储蓄器泄漏。
  • key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱援引,就算未有手动删除,ThreadLocal也会被回收。value在下三次ThreadLocalMap调用set,get,remove的时候会被免除。
    相比较三种状态,大家能够窥见:由于ThreadLocalMap的生命周期跟Thread一样长,借使都并未有手动删除对应key,都会促成内部存款和储蓄器泄漏,可是接纳弱援引可以多一层保险:弱引用ThreadLocal不会内部存款和储蓄器泄漏,对应的value在下一回ThreadLocalMap调用set,get,remove的时候会被拔除

就此,ThreadLocal内部存款和储蓄器泄漏的来源于是:由于ThreadLocalMap的生命周期跟Thread同样长,若无手动删除对应key就能够产生内部存款和储蓄器泄漏,并不是因为弱引用。

能够见到即便访问的是同贰个ThreadLocal对象,不过获取到的值却是不雷同的。

3. ThreadLocal、ThreadLocalMap、Thread 三者之间的涉嫌

从剖析源码的长河中,大家很轻巧的可以预知以下关系:
ThreadLocalMap 是 ThreadLocal 的里边类,Thread 中有个 ThreadLocalMap 成员变量 threadLocals

ThreadLocal源码深入分析,以至ThreadLocal、ThreadLocalMap、Thread 三者之间的关系
详解 ThreadLocal
深入分析 ThreadLocal 内部存款和储蓄器泄漏难点
深切剖析ThreadLocal
详细掌握ThreadLocal变量

那正是说为啥会形成那样的结果吗?那就需求去探视ThreadLocal的源码达成,这里的源码版本为API 28。首要看它的get和set方法。

set方法:

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

set方法中首先获得了前段时间线程对象,然后经过getMap方法传入当前线程t获取到多少个ThreadLocalMap,接下去判定那些map是或不是为空,不为空就直接将日前ThreadLocal作为key,set方法中传来要封存的值最为value,贮存到map中;尽管map为空就调用createMap方法创设三个map并一样将近期ThreadLocal和要保存的值作为key和value参预到map中。

吸纳先看getMap方法:

 ThreadLocalMap getMap { return t.threadLocals;}

getMap方法比较轻松,正是回来从传播的当前线程对象的积极分子变量threadLocals。

接着是createMap方法:

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

createMap方法也很简单便是new了三个ThreadLocalMap并赋给当下线程对象t中的threadLocals。

原本那几个Map是寄放在Thread类中的。于是进入Thread类中查阅。

Thread.java第188-190行:

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

根据这里的注释能够摸清,每一个线程Thread中都有多个ThreadLocalMap类型的threadLocals成员变量来保存数据,通过ThreadLocal类来进行保养。那样看来大家每一回在区别线程调用ThreadLocal的set方法set的多寡是存在分歧线程的ThreadLocalMap中的,就好像注释说的ThreadLocal只是起了个保险ThreadLocalMap的功用。想到是get方法一致也是到不相同线程的ThreadLocalMap去取数据。

get方法:

 public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap; if (map != null) { ThreadLocalMap.Entry e = map.getEntry; if (e != null) { @SuppressWarnings("unchecked") T result = e.value; return result; } } return setInitialValue();}

果真,get方法中一律是先取稳妥前线程对象,然后在拿着这么些指标t去获得到t中的ThreadLocalMap,只要map不对等null就调用map.getEntry方法来获取数据,因为ThreadLocalMap里使用一个之中类Entry来积攒数据的,所以调用getEntry方法,传入的key是日前的ThreadLocal。那样获取到Entry类型数据e,只要e不为null,再次回到e.value即先前积累的数码。如若得到到的map为null又可能依附key获取Entry为null,就调用setInitialValue方法初叶化三个value再次回到。

setInitialValue和initialValue方法:

private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap; if (map != null) map.set(this, value); else createMap; return value;} protected T initialValue() { return null;}

setInitialValue方法中率先调用initialValue方法开首化了贰个空value,之后的操作和set方法一致,将这么些空的value到场到方今线程的ThreadLocalMap中去,ThreadLocalMap为空就成立个Map,最后回到这几个空值。

迄今结束,ThreadLocal的get、set方法就都看过了,也清楚了ThreadLocal能够在多少个线程中操作而互不苦恼的由来。可是ThreadLocal还会有三个要注意的地点正是ThreadLocal使用不当会促成内部存款和储蓄器泄漏。

内部存款和储蓄器泄漏的根本原因是当三个对象已经无需再使用相应被回收时,别的一个正值利用的指标具备它的引用进而形成它不可能被回收,导致本该被回收的对象不能被回收而滞留在堆内部存款和储蓄器中。那么ThreadLocal中是在哪儿发生的啊?那将要见到ThreadLocalMap中积累数据的此中类Entry。

 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super; value = v; } } 

能够看出那些Entry类,这里的key是采纳了个弱引用,所以因为运用弱援引这里的key,ThreadLocal会在JVM后一次GC回收时候被回收,而变成了个key为null的意况,而外界ThreadLocalMap是无法通过null key来找到相应value的。假如当前线程一贯在运营,那么线程中的ThreadLocalMap也就一向存在,而map中却存在key已经被回收为null对应的Entry和value却直接存在不会被回收,产生内部存款和储蓄器的走漏。

不过,那或多或少设计者也思索到了,在get、remove()方法调用的时候会解决掉线程ThreadLocalMap中全部Entry中Key为null的Value,并将总体Entry设置为null,那样在下一次回收时就能够将Entry和value回收。

这么看上去好疑似因为key使用了弱援用才招致的内部存款和储蓄器泄漏,为了化解还非常增添了扫除null key的法力,那么是或不是不用弱引用就足以了啊?

很分明不是如此的。设计者使用弱援引是由原因的。

  • 假诺使用强援引,那么只要在运维的线程中ThreadLocal对象已经被回收了而是ThreadLocalMap还怀有ThreadLocal的强援用,若无手动删除,ThreadLocal不会被回收,一样导致内存泄漏。
  • 假使使用弱援引ThreadLocal的对象被回收了,因为ThreadLocalMap持有的是ThreadLocal的弱援用,纵然未有手动删除,ThreadLocal也会被回收。nullkey的value在下二回ThreadLocalMap调用set,get,remove的时候会被破除。所以,由于ThreadLocalMap和线程Thread的生命周期同样长,若无手动删除Map的中的key,无论采用强援引照旧弱援用实际上都会见世内存泄漏,不过使用弱援引能够多一层保证,nullkey在下二回ThreadLocalMap调用set、get、remove的时候就能被解除。

进而,ThreadLocal的内部存款和储蓄器内泄漏的真正原因并无法算得因为ThreadLocalMap的key使用了弱引用,而是因为ThreadLocalMap和线程Thread的生命周期同样长,没有手动删除Map的中的key才会招致内部存款和储蓄器泄漏。所以化解ThreadLocal的内部存款和储蓄器泄漏难点就要每一趟使用完ThreadLocal,都要记得调用它的remove()方法来裁撤。

编辑:ca88 本文来源:ca88当地线程,ThreadLocal源码分析

关键词: 亚洲城ca88