ThreadLocal完全解析

本文详细解析了ThreadLocal的工作原理及其实现机制,包括set和get方法的具体流程,并通过一个简单示例展示了如何利用ThreadLocal在多线程环境中管理线程局部变量。


简单实例

当一个变量的作用域在一个线程内的时候用ThreadLocal最方便了。
ThreadLocal里面把一个变量复制了多个副本用作每个线程的初始值。
一个简单例子:

public class TestThreadLocal {

    public static void main(String[] args){
        ThreadLocal<Cat> myThreadLocal = new ThreadLocal<Cat>();

        Cat cat = new Cat("白猫");
        myThreadLocal.set(cat);
        System.out.println("main:"+myThreadLocal.get().name); 

        Thread thread1 = new Thread("thread1"){
            @Override
            public void run() {
                int i = 0;
                while(i < 5){
                    myThreadLocal.set( new Cat("黑猫"));
                    System.out.println("thread1:"+myThreadLocal.get().name);
                    i++;
                }
            }
        };

        Thread thread2 = new Thread("thread2"){
            @Override
            public void run() {
                int i = 0;
                while(i < 5){
                    myThreadLocal.set( new Cat("黄猫"));
                    System.out.println("thread2:"+myThreadLocal.get().name);
                    i++;
                }

            }
        };

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

        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("main:"+myThreadLocal.get().name); 
    }


}

输出结果:

main:白猫
thread1:黑猫
thread1:黑猫
thread1:黑猫
thread1:黑猫
thread2:黄猫
thread1:黑猫
thread2:黄猫
thread2:黄猫
thread2:黄猫
thread2:黄猫
main:白猫

可以看出Thread1的Cat(黑猫)并不影响Thread2的Cat(黄猫),同样也不影响main线程的Cat(白猫)。

从上面我们可以看出,ThreadLocal里面存了不同的变量,不同的线程取出来的是不同的实例对象。
那么这有什么用呢?
一般来说,当某些数据是以线程为作用域并且不同线程具有不容的数据副本的时候,就可以考虑使用ThreadLocal。比如对于Handler来说,它需要获取当前线程的Looper,那么显然Looper作用域就是线程,并且不同线程具有不同的Looper,这时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。如果不这样做,那系统就得提供一个表供Handler查找指定线程的Looper,这样一来就又得提供一个LooperManager的类了,但是我们Android系统并没有这么做而是选择了ThreadLocal,这就体现了ThreadLocal的好处了。

ThreadLocal解析

ThreadLocal有以下几个基本的方法:

  • (1) void set(Object value)设置当前线程的线程局部变量的值。
  • (2) public Object get()该方法返回当前线程所对应的线程局部变量。
  • (3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  • (4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。

set路线

我们先来看看ThreadLocal的set()这一条线路:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//根据当前线程取出ThreadLocalMap对象
        if (map != null)
            map.set(this, value);//给value重新赋值
        else
            createMap(t, value);//如果没有ThreadLocalMap数据,就去创建一个ThreadLocalMap对象
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

在Thread类中有个ThreadLocalMap变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

根据当前线程取出对应的ThreadLocalMap,然后把ThreadLocalMap里对应的value值给重新设置,如果当前线程没有设置ThreadLocalMap数据,就去创建。

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

再看看ThreadLocalMap

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

ThreadLocalMap里面创建了一个Entry,并把当前的ThreadLocalMap对象作为key,要存储的数据对象作为值保存起来。
接着我们再看看ThreadLocalMap的set方法:

        private 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)]) {//遍历Entry数组
                ThreadLocal k = e.get();

                if (k == key) {//找到指定的key,并对key对应的value进行赋值
                    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();
        }

ThreadLocalMap的set方法里面就是简单的遍历数组,然后对value进行赋值。

get路线

看完set路线,我们再来看看ThreadLocal的get路线:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//根据当前线程取出对应的ThreadLocalMap对象
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//从ThreadLocalMap对象中取出对应的Entry
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;//然后从Entry中把值取出返回
                return result;
            }
        }
        return setInitialValue();//如果事先没有调用set或者实现initialValue方法,这里将返回null;
    }

顺便看看ThreadLocalMap的getEntry方法:

private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1);//根据HashCode码取出数组里的Entry对象
    Entry e = table[i];
    if (e != null && e.get() == key)//取出Entry对象里面的value值并返回
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

get路线很简单,也不用多说。

总结

1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
最后按照我的惯例,来个脑图总结一下:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值