深入理解ThreadLocal

转载请注明出处:http://blog.csdn.net/fishle123/article/details/48087753

在Android里面,在不同的线程(假设子线程已经创建了Looper)中创建Handler的时候,并不需要显式指定Looper,系统能自动找到改线程自己的Looper。不同线程的Looper相互独立,之所以能做到这一点,就是借助ThreadLocal来实现的。下面结合源码来分析ThreadLocal的使用及实现原理。

1 ThreadLocal的使用

先来看一个ThreadLocal使用的例子:

public class ThreadLocalTest {
	static ThreadLocal mThreadLocal = new ThreadLocal<Long>();
	static long id=0;

	public ThreadLocalTest() {
		id = Thread.currentThread().getId();
		mThreadLocal.set(id);
	}

	public void printValue() {

		System.out.println("Thread " + Thread.currentThread().getId() + ":\t value=" + mThreadLocal.get() + "\t id=" + id);
	}

	static class PrintValueRunnable implements Runnable {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			ThreadLocalTest test = new ThreadLocalTest();

			test.printValue();

		}
	}

	public static void main(String[] args) {
		new Thread(new PrintValueRunnable()).start();

		new Thread(new PrintValueRunnable()).start();
	}
}

上面这段程序向我们展示了 ThreadLocal 的基本用法。这个例子很简单,定义了 2 个类变量 mThreadLocal id ,一个成员方法 printValue 用来打印 mThreadLocal id 的值,定义一个 PrintValueRunnable run 方法中会先 new 一个 ThreadLocalTest 的实例,然后调用 printValue 来打印值。最后在 main 函数里面,创建两个线程,在线程里面执行 PrintValueRunnable 来执行打印任务。在 ThreadLocalTest 因为 mThreadLocal id 都是类成员变量,所以在两个线程中都可以访问。在 ThreadLocalTest 的构造函数中会分别将线程 id 保存到 mThreadLocal id 中。下面先看一下打印结果:

 

 从打印结果可以看到:mThreadLocal中保存时每个线程的ID,但是id保存的是同一个ID值。因为id是两个进程内共享的,所以id的值是后一个执行的线程的idmThreaLocal也是进程内共享的,但是mThreadLocal.get()能保证不同线程间调用mThreadLocal.set()进来的值互不干扰。所以,当每个线程是用ThreadLocal来保存Looper的时候,可以保证在任何时候任何地方创建的Handler都能正确的拿到当前线程的Looper

ThreadLocal是怎么做到这一点的呢?

 2 ThreadLocal的实现原理分析

 其实在Thread中有一个hash结构的ThreadLocal.ThreadLocalMap对象的实例threadLocalsthreadLocals中才是真正保存变量的地方。ThreadLocal负责管理threadLocals中保存的变量。

下面结合ThreadLocal的源码来分析它的实现原理。先来看ThreadLocalsetget方法:

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);
    }
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

可以看到,在set中以当前线程的实例为参数调用getMap获取当前线程的ThreadLocalMap对象,如果线程的threadLocals不为null,就将value保存到threadLocals中,反之,先创建一个ThreadLocalMap实例。ThreadLocal.get()也是从threadLocals中来读取value。从这儿也可以看出,我们想要隔离的value并不是保存到ThreadLocal中,而是在每个线程对象的内部来保存。因为是每个线程自己来保存value,所以做到了线程间相互隔离。

5行代码需要我们注意,在set中保存value的时候,是以this即当前ThreadLocal实例为键,以待保存的变量value为值来保存的。

下面来看一下ThreadLocalMap的具体实现,先看构造函数:

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

在构造函数中,先new一个Entry数组,默认大小INITIAL_CAPACITY16。随着元素增多,会动态调整其大小,但都是2n。然后根据ThreadLocal.threadLocalHashCode来计算hash值,具体算法如第8行所示。再看一下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继承与WeakReferenceThreadLocalMap中的键ThreadLocal对象通过软引用来保存,值则保存到一个Object的实例value中。

再来看一下调用set/get时,ThreadLocalMap是怎样查找对象的,通过前面的代码知道调用ThreadLoca.set的时候是通过调用ThreadLocalMap.set来完成的,先看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();
        }
 
private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

set中,先在第10行计算hash值,并作为在table中查找的起点。在查找的过程中,如果当前Entrykey与待插入的key相同,则直接更新value。如果找到一个空的Entry,则退出循环,然后插入value。通过nextIndex可以看到,如果插入的时候发生碰撞,那么采用线性探查来计算下一个位置。在查找的过程中,如果遇到keynullEntry(因为key是一个软引用,所以每次gc之后就可能有keynull),如第22行所示,会调用replaceStaleEntry继续查找,如果没有更合适的,则插入到当前位置。插入完成如果有必要,会对hash表进行清理:删除掉keynullEntry,并对受影响的部分进行重新hash

再看一下ThradLocalMapget操作:

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

getEntry 很简单,先计算 hash 值,然后比较 entry 中保存的 key 是否与待查找的 key 一致,如果是则直接返回,反之,按照线性探查法继续查找。查找的过程中,如果发现有 Entry key null ,则会调用 expungeStaleEntry 进行清理,并对受影响的部分重新 hash


到此为止,已经分析完ThradLocal的实现原理:

Thread中有一个hash结构的ThreadLocal.ThreadLocalMap对象的成员变量threadLocalsthreadLocals中以ThreadLocal对象为key,以要隔离的值为value(即调用threadLocal.set(value)中的value)ThreadLocal负责管理threadLocals中保存的变量。

3 InheritableThreadLocal的使用

通过上面的分析知道通过ThreadLocal保存的值是线程隔离的。其实在Thread对象中,还有一个ThreadLocal.ThreadLocalMap类型的成员变量inheritableThreadLocalsinheritableThreadLocals其实是一个InheritableThreadLocals类型,InheritableThreadLocalsThreadLocal的子类。保存在inheritableThreadLocals中的值可以传递给子线程。下面看一个例子:

public class InheritableThreadLocalTest {
static InheritableThreadLocal<Long> mInheritableThreadLocal = new InheritableThreadLocal<Long>();
static ThreadLocal<Long> mThreadLocal = new ThreadLocal<Long>();
static SimpleInheritableThreadLocal<Long> mSimpleInheritableThreadLocal=new SimpleInheritableThreadLocal<Long>();
public static void printValue() {
 
System.out.println("Thread " + Thread.currentThread().getId() + ":\t valueFromParent=" + mInheritableThreadLocal.get() + "\t valueFromLocal="
+ mThreadLocal.get() +"\tsimpleValueFromParent="+mSimpleInheritableThreadLocal.get());
}
 
static class PrintValueRunnable implements Runnable {
 
@Override
public void run() {
// TODO Auto-generated method stub
InheritableThreadLocalTest.printValue();
if (Thread.currentThread().getId() % 2 == 0) {
mInheritableThreadLocal.set(mInheritableThreadLocal.get()+1);
}
InheritableThreadLocalTest.printValue();
}
}
 
public static void main(String[] args) {
 
long tid = Thread.currentThread().getId();
mInheritableThreadLocal.set(tid);
mThreadLocal.set(tid);
mSimpleInheritableThreadLocal.set(tid);
System.out.println("mainThread: " + "\t valueFromLocal=" + mThreadLocal.get());
new Thread(new PrintValueRunnable()).start();
new Thread(new PrintValueRunnable()).start();
new Thread(new PrintValueRunnable()).start();
}
}

打印结果如下:

 

在这个例子,先在主线程中new一个InheritableThreadLocal对象,然后在子线程中访问。通过打印结果可以看到,主线程设置到InheritableThreadLocal中的值会复制给子线程,然后父子线程可以各自独立读写保存在InheritableThreadLocal中的值。可以还有人注意到了,在上面的例子中还有一个SimpleInheritableThreadLocal对象,这个是我自定义,它继承了InheritableThreadLocal,并重写了InheritableThreadLocal.childValue。我们可以通过重写childValue来控制从父线程中继承过来的value的初始值。在SimpleInheritableThreadLocal中会对从父线程继承的Long对象做加1处理。默认情况下,是不做任何处理的。SimpleInheritableThreadLocal的代码如下:

public class SimpleInheritableThreadLocal<T> extends InheritableThreadLocal<T> {
 
@Override
protected T childValue(T parentValue) {
// TODO Auto-generated method stub
if (parentValue instanceof Long) {
Long res = (Long) parentValue + 1;
return (T) res;
}
 
return super.childValue(parentValue);
}
}

可能有人会有疑问:ThreadLocal是线程隔离的,而InheritableThreadLocal又是ThreadLocal的子类,所以InheritableThreadLocal也应该是线程隔离的。那这里是怎么做到从父线程继承值呢?子线想一想:实现线程隔离的核心思想是将要隔离的变量保存到Thread对象里面,然后通过ThreadLocal来读写这个变量;其实不通过ThreadLocal,在Thread内部是可以直接读写threadLocals的。那既然inheritableThreadLocalsThread的成员变量,那么在初始化Thread的时候就可以直接读取父线程的inheritableThreadLocals并把它保存的values设置为子线程的inheritableThreadLocals初始值。

这个想法对不对呢?我们来看一下Thread初始化部分的源码:

public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
 
        this.name = name.toCharArray();
 
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */
 
            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }
 
            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
 
        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();
 
        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
 
        g.addUnstarted();
 
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
 
        /* Set thread ID */
        tid = nextThreadID();
}

先看Thread的构造函数,这里调用了init函数,最终会调用这init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc)函数。直接看第60行,如果父线程的parent.inheritableThreadLocals不为null,就会以父线程的inheritableThreadLocals为参数来构造子线程的inheritableThreadLocals。我们再看一下createInheritedMap函数:

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
}
 private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
 
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    ThreadLocal key = e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

到这里就可以看到:在ThreadLocalMap的构造函数中会遍历保存父线程的inheritableThreadLocals中键值对,并且在读取value的会调用key.childValue计算子线程中的value。所以在SimpleInheritableThreadLocal重写了childValue来根据父线程的value计算子线程的value,默认情况是不做任何处理,直接返回的,如:

T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

总结

到此为止,本文介绍了ThreadLocal的使用,及其实现原理。最后还介绍了InheritThreadLocal的使用。在Android中,ThreadLocal使用的一个例子就是Looper,有兴趣的可以看一下Looper的实现原理。或者参考另一篇对Looper分析的blogAndroid消息机制分析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值