zoukankan      html  css  js  c++  java
  • ThreadLocal源码分析(转)

    阅读总结:

      ThreadLocal内部使用静态map存储,每个变量对应一个hashcode,不需要指定key值,后台动态生成,good!

      每个变量ThreadLocal内部分配Entry,获取值时,通过变量找到Entry,找到对应hashcode,获取值;

      设置值同理。

      init部分,有点晕忽,写的乱七八糟,查了下源代码,其实就是线程初始化的时候,新建了个ThreadLocalMap变量,和什么子线程父线程木有任何关系。

      

    在阅读《Java Concurrency In Practice》时,书中提到ThreadLocal是一种更为规范常用的Thread Confine方式。于是想仔细分析一下ThreadLocal的实现方式。曾经转载了一篇关于ThreadLocal的文章:hi.baidu.com/gefforey520/blog/item/c3bb64fa4ad1779358ee902c.html,其中提到ThreadLocal的实现方式是声明一个Hashtable,然后以Thread.currentThread()为key,变量的拷贝为value。今天阅读源码才知道实现方式已经大为改变,下面来看代码。
    /**
    * ThreadLocals rely on per-thread linear-probe hash maps attached to each
    * thread (Thread.threadLocals and inheritableThreadLocals). The ThreadLocal
    * objects act as keys, searched via threadLocalHashCode. This is a custom
    * hash code (useful only within ThreadLocalMaps) that eliminates collisions
    * in the common case where consecutively constructed ThreadLocals are used
    * by the same threads, while remaining well-behaved in less common cases.
    */
    private final int threadLocalHashCode = nextHashCode();

    /**
    * The next hash code to be given out. Updated atomically. Starts at zero.
    */
    private static AtomicInteger nextHashCode = new AtomicInteger();

    /**
    * The difference between successively generated hash codes - turns implicit
    * sequential thread-local IDs into near-optimally spread multiplicative
    * hash values for power-of-two-sized tables.
    */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
    * Returns the next hash code.
    */
    private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    /**
    * Creates a thread local variable.
    */
    public ThreadLocal() {
    }
    ThreadLocal只有三个变量,从构造函数知道,在创建一个ThreadLocal实例时,只是调用nextHashCode方法将nextHashCode的值赋给实例的threadLocalHashCode,然后nextHashCode的值增加HASH_INCREMENT这个值。 因此ThreadLocal实例的变量只有threadLocalHashCode,而且是final的,用来区分不同的ThreadLocal实例。
    再来看其get方法:
    /**
    * Returns the value in the current thread's copy of this thread-local
    * variable. If the variable has no value for the current thread, it is
    * first initialized to the value returned by an invocation of the
    * {@link #initialValue} method.

    * @return the current thread's value of this thread-local
    */
    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();
    }
    其中调用getMap(Thread t)返回ThreadLocalMap,ThreadLocalMap是内部静态类,部分代码如下:
    /**
    * ThreadLocalMap is a customized hash map suitable only for maintaining
    * thread local values. No operations are exported outside of the
    * ThreadLocal class. The class is package private to allow declaration of
    * fields in class Thread. To help deal with very large and long-lived
    * usages, the hash table entries use WeakReferences for keys. However,
    * since reference queues are not used, stale entries are guaranteed to be
    * removed only when the table starts running out of space.
    */
    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;
    }
    }

    /**
    * The initial capacity -- MUST be a power of two.
    */
    private static final int INITIAL_CAPACITY = 16;

    /**
    * The table, resized as necessary. table.length MUST always be a power
    * of two.
    */
    private Entry[] table;

    /**
    * The number of entries in the table.
    */
    private int size = 0;

    /**
    * The next size value at which to resize.
    */
    private int threshold; // Default to 0
    Entry继承WeakReference,通过其注释并结合WeakReference的功能,我们知道:一旦没有指向 key 的强引用, ThreadLocalMap 在 GC 后将自动删除相关的 entry。ThreadLocalMap采用数组来保存Entry,并且Entry中以ThreadLocal为key,初始大小为16.
            接着看ThreadLocalMap的constructor:
    /**
    * 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);
    }
    /**
    * Construct a new map including all Inheritable ThreadLocals from given
    * parent map. Called only by createInheritedMap.

    * @param parentMap
    *            the map associated with parent thread.
    */
    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有两个构造函数,可以直接传入ThreadLcoal-value对,也可以传入一个ThreadLocalMap,传入ThreadLocalMap的时候,会依次将其Entry存放在table中。接着来分析get方法:
    /**
    * Get the entry associated with key. This method itself handles only
    * the fast path: a direct hit of existing key. It otherwise relays to
    * getEntryAfterMiss. This is designed to maximize performance for
    * direct hits, in part by making this method readily inlinable.

    * @param key
    *            the thread local object
    * @return the entry associated with key, or null if no such
    */
    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);
    }
    通过实例变量threadLocalHashCode算出下标,然后返回其值。set和remove方法类似。
    继续看ThreadLocal类的get方法,通过getMap(Thread t)返回ThreadLocalMap,然后从ThreadLocalMap中通过getEntry(ThreadLocal key) 取出值。下面继续看getMap(Thread t)方法:
    /**
    * Get the map associated with a ThreadLocal. Overridden in
    * InheritableThreadLocal.

    * @param t
    *            the current thread
    * @return the map
    */
    ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
    }
    可以看出其返回的是线程的一个实例变量。由此可知Thread类也持有ThreadLocalMap,这样每个线程的变量都存放在自己的ThreadLocalMap中,可谓名符其实。
    继续看Thread类如何操作ThreadLocalMap:
    /*
    * ThreadLocal values pertaining to this thread. This map is maintained by
    * the ThreadLocal class.
    */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
    * InheritableThreadLocal values pertaining to this thread. This map is
    * maintained by the InheritableThreadLocal class.
    */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    Thread类中声明了两个ThreadLocalMap变量,
    /**
    * Initializes a Thread.

    * @param g
    *            the Thread group
    * @param target
    *            the object whose run() method gets called
    * @param name
    *            the name of the new Thread
    * @param stackSize
    *            the desired stack size for the new thread, or zero to indicate
    *            that this parameter is to be ignored.
    */
    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
    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();
    this.name = name.toCharArray();
    if (security == null || isCCLOverridden(parent.getClass()))
    this.contextClassLoader = parent.getContextClassLoader();
    else
    this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext = 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();
    }
    在init方法中,存在着对inheritableThreadLocals的操作:
    if (parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    而ThreadLocal的createInheritedMap方法则是调用ThreadLocalMap类传入ThreadLocalMap参数的构造函数。
    也就是说在Thread类中,当前线程会调用init方法去初始一个线程,而在init方法中,会将当前线程的inheritableThreadLocals拷贝给等待初始化的线程。这让我联想起unix/linux系统中,父线程会调用fork()函数生成一个子线程,而且会把父线程大部分的信息拷贝给子线程。
             最后来看Thread类的exit方法:
    /**
    * This method is called by the system to give a Thread a chance to clean up
    * before it actually exits.
    */
    private void exit() {
    if (group != null) {
    group.remove(this);
    group = null;
    }
    /* Aggressively null out all reference fields: see bug 4006245 */
    target = null;
    /* Speed the release of some of these resources */
    threadLocals = null;
    inheritableThreadLocals = null;
    inheritedAccessControlContext = null;
    blocker = null;
    uncaughtExceptionHandler = null;
    }
    在线程真正终止前会执行这个方法,这个方法会把threadLocals和inheritableThreadLocals指向null。但我在Thread类中并没有看到对threadLocals的赋值,应该是通过ThreadLocal来设置的。
            写了个简单的Thread测试程序,只是想跟踪一下上述两个ThreadLocalMap变量的状态:
    public class TimePrinter extends Thread {

    public void run() {
    while (true) {
    try {
    System.out.println(new Date(System.currentTimeMillis()));
    } catch (Exception e) {
    System.out.println(e);
    }
    }
    }

    static public void main(String args[]) {
    TimePrinter tp1 = new TimePrinter();
    tp1.start();
    ThreadLocal t2 = new ThreadLocal();
    t2.set("aaaaaaaaaaaaaaaaaaaaaaaa");
    }
    }
    可以看到,启动一个线程,不停打印系统时间,然后通过ThreadLocal给当前线程添加一份字符串,观察有:




    inheritableThreadLocals中有一个Entry,但value为null,threadLocals中有三个Entry,其中两个value不明,一个为ThreadLocal设置的值。不过我实在不知道其他三个Entry值是如何设置的,留个疑问。
    总结:ThreadLocal实例只有一个threadLocalHashCode值,ThreadLocal给各个线程设置的值都是存在各个线程threadLocals里 。相比Hashtable的实现方式,现在的方式更为合理。当一个线程终止时,其inheritableThreadLocals和threadLocals均被置为null,于是通过TreadLocal也就无法访问这个线程;而当ThreadLocal被设置为null时,Thread里threadLocals就会移除key为ThreadLocal的Entry。Hashtable的实现方式则无法实现这一点。最为关键的是Hashtable的实现需要同步,所带来的性能损耗是很大的,而现在的方式则不需要同步。性能提升很大。

    计划、执行、每天高效的活着学着
  • 相关阅读:
    Python学习笔记(6)-异常
    Python学习笔记(1)-基本概念
    自动化测试工具Sikuli的安装以及应用
    SoapUI添加断言
    SoapUI接口之间的数据传递
    Fitnesse初体验
    SoapUI5.1.2命令行执行https类型接口集成
    SoapUI命令行执行测试用例
    Jenkins集成AirTest不启动浏览器
    AirTest执行时不能输入汉字
  • 原文地址:https://www.cnblogs.com/huxiaoyun90/p/3293172.html
Copyright © 2011-2022 走看看