zoukankan      html  css  js  c++  java
  • 深入理解ThreadLocal(一)

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

    1 ThreadLocal的使用

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

    [java] view plain copy 

    1. public class ThreadLocalTest {  

    2.     static ThreadLocal mThreadLocal = new ThreadLocal<Long>();  

    3.     static long id=0;  

    4.   

    5.     public ThreadLocalTest() {  

    6.         id = Thread.currentThread().getId();  

    7.         mThreadLocal.set(id);  

    8.     }  

    9.   

    10.     public void printValue() {  

    11.   

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

    13.     }  

    14.   

    15.     static class PrintValueRunnable implements Runnable {  

    16.   

    17.         @Override  

    18.         public void run() {  

    19.             // TODO Auto-generated method stub  

    20.             ThreadLocalTest test = new ThreadLocalTest();  

    21.   

    22.             test.printValue();  

    23.   

    24.         }  

    25.     }  

    26.   

    27.     public static void main(String[] args) {  

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

    29.   

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

    31.     }  

    32. }  

    上面代码展示了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的值是后一个执行的线程的id。mThreaLocal也是进程内共享的,但是mThreadLocal.get()能保证不同线程间调用mThreadLocal.set()进来的值互不干扰。所以,当每个线程是用ThreadLocal来保存Looper的时候,可以保证在任何时候任何地方创建的Handler都能正确的拿到当前线程的Looper。

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

     2 ThreadLocal的实现原理分析

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

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

    [java] view plain copy

    1. public void set(T value) {    

    2.         Thread t = Thread.currentThread();    

    3.         ThreadLocalMap map = getMap(t);    

    4.         if (map != null)    

    5.             map.set(this, value);    

    6.         else    

    7.             createMap(t, value);    

    8.     }    

    9.    

    10.  ThreadLocalMap getMap(Thread t) {  

    11.         return t.threadLocals;  

    12.     }  

    13.    

    14.  void createMap(Thread t, T firstValue) {  

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

    16.     }  

    17. public T get() {  

    18.         Thread t = Thread.currentThread();  

    19.         ThreadLocalMap map = getMap(t);  

    20.         if (map != null) {  

    21.             ThreadLocalMap.Entry e = map.getEntry(this);  

    22.             if (e != null)  

    23.                 return (T)e.value;  

    24.         }  

    25.         return setInitialValue();  

    26.     }  

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

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

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

    [java] view plain copy 

    1. /** 

    2.          * Construct a new map initially containing (firstKey, firstValue). 

    3.          * ThreadLocalMaps are constructed lazily, so we only create 

    4.          * one when we have at least one entry to put in it. 

    5.          */  

    6.         ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {  

    7.             table = new Entry[INITIAL_CAPACITY];  

    8.             int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);  

    9.             table[i] = new Entry(firstKey, firstValue);  

    10.             size = 1;  

    11.             setThreshold(INITIAL_CAPACITY);  

    12.         }  

    在构造函数中,先new一个Entry数组,默认大小INITIAL_CAPACITY为16。随着元素增多,会动态调整其大小,但都是2n。然后根据ThreadLocal.threadLocalHashCode来计算hash值,具体算法如第8行所示。再看一下Entry的定义:

    [java] view plain copy 

    1. static class Entry extends WeakReference<ThreadLocal> {  

    2.            /** The value associated with this ThreadLocal. */  

    3.            Object value;  

    4.   

    5.            Entry(ThreadLocal k, Object v) {  

    6.                super(k);  

    7.                value = v;  

    8.            }  

    9.        }  

    可以看到Entry继承与WeakReference,ThreadLocalMap中的键ThreadLocal对象通过软引用来保存,值则保存到一个Object的实例value中。

    再来看一下调用set/get时,ThreadLocalMap是怎样查找对象的,通过前面的代码知道调用ThreadLoca.set的时候是通过调用ThreadLocalMap.set来完成的,先看set:

    [java] view plain copy 

    1. private void set(ThreadLocal key, Object value) {  

    2.    

    3.             // We don't use a fast path as with get() because it is at  

    4.             // least as common to use set() to create new entries as  

    5.             // it is to replace existing ones, in which case, a fast  

    6.             // path would fail more often than not.  

    7.    

    8.             Entry[] tab = table;  

    9.             int len = tab.length;  

    10.             int i = key.threadLocalHashCode & (len-1);  

    11.    

    12.             for (Entry e = tab[i];  

    13.                  e != null;  

    14.                  e = tab[i = nextIndex(i, len)]) {  

    15.                 ThreadLocal k = e.get();  

    16.    

    17.                 if (k == key) {  

    18.                     e.value = value;  

    19.                     return;  

    20.                 }  

    21.    

    22.                 if (k == null) {  

    23.                     replaceStaleEntry(key, value, i);  

    24.                     return;  

    25.                 }  

    26.             }  

    27.    

    28.             tab[i] = new Entry(key, value);  

    29.             int sz = ++size;  

    30.             if (!cleanSomeSlots(i, sz) && sz >= threshold)  

    31.                 rehash();  

    32.         }  

    33.    

    34. private static int nextIndex(int i, int len) {  

    35.             return ((i + 1 < len) ? i + 1 : 0);  

    36.         }  

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

    再看一下ThradLocalMap的get操作:

    [java] view plain copy 

    1. private Entry getEntry(ThreadLocal key) {  

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

    3.             Entry e = table[i];  

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

    5.                 return e;  

    6.             else  

    7.                 return getEntryAfterMiss(key, i, e);  

    8.         }  

    9.    

    10.  private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {  

    11.             Entry[] tab = table;  

    12.             int len = tab.length;  

    13.    

    14.             while (e != null) {  

    15.                 ThreadLocal k = e.get();  

    16.                 if (k == key)  

    17.                     return e;  

    18.                 if (k == null)  

    19.                     expungeStaleEntry(i);  

    20.                 else  

    21.                     i = nextIndex(i, len);  

    22.                 e = tab[i];  

    23.             }  

    24.             return null;  

    25.         }  

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

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

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

  • 相关阅读:
    2017-2018-1 20155218 《信息安全系统设计基础》第十一周学习总结
    # 课堂测试(CH6)20155218
    Elasticsearch6.0 IKAnalysis分词使用
    ELK6.0环境搭建及配置
    使用PHP抓取网站ico图标
    centos下apache+mysql+php安装及配置
    PHP的(Thread Safe与Non Thread Safe)
    作业九
    附加题
    作业八
  • 原文地址:https://www.cnblogs.com/downey/p/5302079.html
Copyright © 2011-2022 走看看