详解ThreadLocal

一、首先要了解ThreadLocal是什么东西,这个有什么用?

ThreadLocal是jdk 1.2开始提供的一个类,作用是为了解决在多线程中并发访问的一种方式,为每一个线程提供一个独立的副本,这个副本只对当前线程可见,其他线程不能访问,从而隔离了多个线程对数据的访问冲突。在Android应用中我们会经常看到他的影子,如Handler中,EventBus中都有用到它。

二、再来看看它的使用:
class MainClass {

    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(new TestRunnable());
        Thread thread2 = new Thread(new TestRunnable());

        thread1.start();
        thread2.start();

    }


    private static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };


    public static class TestRunnable implements Runnable {

        @Override
        public void run() {

            for (int i = 0; i < 5; i++) {
                local.set(local.get() + 1);
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "===" + local.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

输出结果如下:
输出结果

上面的代码是开启了两个线程,并且在线程了对同一个ThreadLocal实例变量local进行了get和set的操作。从上面的结果可以看到两个线程内的local的get和set互不影响。为什么会出现这样的结果呢?
下图是它的原理图:

原理图

好,我们根据上图去查看它的源码分析。

三、ThreadLocal的源码分析。

1、首先看它的构造函数:

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

2、然而它的构造函数什么也没有,我们上面的例子用到了它的get()和set()方法,那我们去看一下它的get()方法的实现。

public T get() {
    Thread t = Thread.currentThread();   //获取当前的线程
    ThreadLocalMap map = getMap(t);  //通过线程拿到当前线程的ThreadLocalMap 
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);    // 通过this去拿到ThreadLocalMap的值,this为ThreadLocal对象
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();  //如果当前线程的ThreadLocalMap 为空,则返回默认值
} 

2.1 这里先不管这这个ThreadLocalMap内部是什么样的,我们只知道它是以ThreadLocal为key存取的Entry对象的map,反正它肯定和我们保存的数据有关就是了。
我们来看看getMap()这方法:

ThreadLocalMap getMap(Thread t) {  
    return t.threadLocals;  //噢,原来它是线程实例的一个成员变量,也就是每个线程都持有一个这样的变量,来到这里,我们是否有了一点思路。既然这是每个线程的变量,那这个map存取到的数据肯定是为该线程所有的啦。
}

2.2 然后继续看下去 setInitialValue()方法:

private T setInitialValue() {
    T value = initialValue();  //是不是很熟悉,上面例子创建ThreadLocal时重写了该方法,就是为ThreadLoca的get()方法提供默认值 
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value); 
    return value;
}

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

很简单,就是map为空,则创建ThreadLocalMap实例并赋值给当前线程的threadLocals 变量

3、好了,我们再来看看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,如果不为空就赋值,为空就创建再赋值。

4、来到这里,其实ThreadLocal的原理框架已经很明了啦。那我们再来看看这个ThreadLocalMap内有什么乾坤。ThreadLocalMap是ThreadLocal的静态内部类。

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;
        }
    }
/**
 * Construct a new map initially containing (firstKey, firstValue).
 * ThreadLocalMaps are constructed lazily, so we only create
 * one when we have at least one entry to put in it.
 */
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
..................................
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);
}

//set方法
private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    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();
}
}
 /**
   * Remove the entry for key.
  */
 private void remove(ThreadLocal key) {
     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)]) {
           if (e.get() == key) {
            e.clear();
           expungeStaleEntry(i);
           return;
           }
      }
}

//ThradLocal的扩容
private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;

    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    setThreshold(newLen);
    size = count;
    table = newTab;
}


.........................
}

上面贴出了ThreadLocalMap的主要代码,包含了 get(),set(),remove()等普通数据结构该有的常用方法,也包含了类似HashMap的扩容方法(默认长度16,达到长度75%,扩容两倍,有兴趣可以查看HashMap的源码)。
我们可以看到了这个Entry类:

 static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

Entry将ThreadLocal实例作为key,副本变量value存储起来。注意Entry中对于ThreadLocal实例的引用是一个弱引用,还引用定义在Reference类(WeakReference的父类中),下面是 super(k) 最终调用的代码:

Reference(T referent) {
    this(referent, null);
}

Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

内存引用

这里会出现内存泄漏的情况。
ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。但是当thread结束后,thread就不存在与栈中,强引用会断开,这时thread,map,value都会得到释放被GC回收。所以内存泄漏的情况只是出现在threadLocal置为null而线程还没结束的区间内,而我们正确的写法就是要在使用完之后及时调用ThreadLocal的remove方法去移除对于的Entry。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值