ThreadLocal源码分析

1、概述

ThreadLocal,可以理解为线程的局部变量,作用就是为每一个使用该变量的线程都提供一个变量值的副本,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?

每个线程中都有一个ThreadLocalMap(Thread.threadLocals),用于存储每一个线程的变量的副本。

ThreadLocalMap使用数组Entry[] table保存ThreadLocal-->Object键值对象,数组保存位置:int i = key.nextHashCode() & (table.length - 1);。

ThreadLocal和Synchonized区别:

都用于解决多线程并发访问。

Synchronized用于线程间的数据共享(使变量或代码块在某一时该只能被一个线程访问),是一种以延长访问时间来换取线程安全性的策略;

而ThreadLocal则用于线程间的数据隔离(为每一个线程都提供了变量的副本),是一种以空间来换取线程安全性的策略。

2、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 table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

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

        /**
         * Version of getEntry method for use when key is not found in
         * its direct hash slot.
         *
         * @param  key the thread local object
         * @param  i the table index for key‘s hash code
         * @param  e the entry at table[i]
         * @return the entry associated with key, or null if no such
         */
        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;
        }

        /**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be 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;
                }
            }
        }

        ...... 
    }

3、ThreadLocal

public class ThreadLocal<T> {

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

  /**
     * Returns the current thread‘s "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
   //返回此线程局部变量的当前线程的初始值
   protected T initialValue() {
        return null;
    }

    /**
     * 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 = t.threadLocals;
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * Sets the current thread‘s copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread‘s copy of
     *        this thread-local.
     */
  //将此线程局部变量的当前线程副本中的值设置为指定值
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = t.threadLocals;
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * Removes the current thread‘s value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
   //移除此线程局部变量的值
     public void remove() {
         ThreadLocalMap m = Thread.currentThread().threadLocals;
         if (m != null)
             m.remove(this);
     }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

  ......
}

为什么取HASH_INCREMENT = 0x61c88647?

(可以阅读Why 0x61c88647?)

由来:

This number represents the golden ratio (sqrt(5)-1) times two to the power of 31. The result is then a golden number, either 2654435769 or -1640531527. You can see the calculation here:

long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1));  //Math.sqrt(5) - 1 = 1.2360679774997898
System.out.println("as 32 bit unsigned: " + l1);  //2654435769

int i1 = (int) l1;
System.out.println("as 32 bit signed:   " + i1);  //-1640531527 = -0x61c88647

与fibonacci hashing(斐波那契散列法)以及黄金分割有关,特殊的哈希码0x61c88647大大降低碰撞的几率,能让哈希码能均匀的分布在2的N次方的数组里。

key.threadLocalHashCode & (len-1),ThreadLocalMap 中 Entry[] table 的大小必须是2的N次方呀(len = 2^N),那 len-1 的二进制表示就是低位连续的N个1, 那 key.threadLocalHashCode & (len-1) 的值就是 threadLocalHashCode 的低N位。

测试:

private static AtomicInteger nextHashCode = new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    public static void main(String[] args) {
        for (int j = 0; j < 5; j++) {
            int size = 2 << j;
            // hash = 0;
            int[] indexArray = new int[size];
            for (int i = 0; i < size; i++) {
                indexArray[i] = nextHashCode() & (size - 1);
            }
            System.out.println("indexs = "+ Arrays.toString(indexArray));
        }
    }

结果:

indexs = [0, 1]
indexs = [2, 1, 0, 3]
indexs = [2, 1, 0, 7, 6, 5, 4, 3]
indexs = [2, 9, 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11]
indexs = [18, 25, 0, 7, 14, 21, 28, 3, 10, 17, 24, 31, 6, 13, 20, 27, 2, 9, 16, 23, 30, 5, 12, 19, 26, 1, 8, 15, 22, 29, 4, 11]

没有看到重复的索引值,要哈希表的大小是2的N次方,那么基本上可以保证每次计算出的index值都不会重复。

为什么HashCode不直接用自增的方式(HASH_INCREMENT=1)?

我的理解是,随着不用的 ThreadLocal 变量被回收掉,这种自增的方式的性能会越来越差,因为临近的 slot 为空的可能性很小。而 ThreadLocal 实际所采用的方式,其下标是在跳跃分布,这样即使出现冲突,在临近找到空 slot 的可能性更大一些,性能也会更好。

4、例子

Android Looper的实现:

public final class Looper {     ......

    // sThreadLocal.get() will return null unless you‘ve called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

   ......

}

ThreadId:

维护每个线程的id

public class ThreadId {
        // Atomic integer containing the next thread ID to be assigned
        private static final AtomicInteger nextId = new AtomicInteger(0);

        // Thread local variable containing each thread‘s ID
        private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue () {
                return nextId.getAndIncrement();
            }
        };

        // Returns the current thread‘s unique ID, assigning it if necessary
        public static int get() {
            return threadId.get();
        }
    }
时间: 2024-10-16 08:00:44

ThreadLocal源码分析的相关文章

【JAVA】ThreadLocal源码分析

ThreadLocal内部是用一张哈希表来存储: 1 static class ThreadLocalMap { 2 static class Entry extends WeakReference<ThreadLocal<?>> { 3 /** The value associated with this ThreadLocal. */ 4 Object value; 5 6 Entry(ThreadLocal<?> k, Object v) { 7 super(k)

ThreadLocal 源码分析

1.个人总结和想法: (1).ThreadLocal的内存泄漏问题? ThreadLocal 我们应该关注它的内存泄漏问题,原因虽然JDK开发者已经使用了弱引用的键来尝试解决这个问题,不过是依然存在很大风险的,因为当使用static的ThreadLocal时会使其生命周期和类一样,这样是没有必要的,就造成了内存泄漏,还有我们使用完了没有及时的清除ThreadLocal也会导致内存泄漏,内存泄漏就是让本应该被回收的对象还依然占用着内存. (2)ThreadLocal中的对象关系? ThreadLo

Java -- 基于JDK1.8的ThreadLocal源码分析

1,最近在做一个需求的时候需要对外部暴露一个值得应用  ,一般来说直接写个单例,将这个成员变量的值暴露出去就ok了,但是当时突然灵机一动(现在回想是个多余的想法),想到handle源码里面有使用过ThreadLocal这个类,想了想为什么不想直接用ThreadLocal保存数据源然后使用静态方法暴露出去呢,结果发现使用ThreadLocal有时候会获取不到值,查了下原因原来同事是在子线程中调用的(捂脸哭泣),所以还是要来看一波源码,看看ThreadLocal底层实现,适用于哪些场景 2,我们现在

Threadlocal源码分析以及其中WeakReference作用分析

今天在看Spring 3.x企业应用开发实战,第九章 Spring的事务管理,9.2.2节ThreadLocal的接口方法时,书上有提到Threadlocal的简单实现,我就去看了下JDK1.8的Threadlocal的源码.发现实现方式与书中讲的并不相同,同时在网上搜索了一下,发现有比较多的人理解错了. 先看一下容易误导的解释:在ThreadLocal类中有一个Map对象,这个Map以每个Thread对象为键,保存了这个线程对应局部变量值,对应的实现方式如下: public class Sim

并发编程(四):ThreadLocal从源码分析总结到内存泄漏

一.目录 1.ThreadLocal是什么?有什么用? 2.ThreadLocal源码简要总结? 3.ThreadLocal为什么会导致内存泄漏? 二.ThreadLocal是什么?有什么用? 引入话题:在并发条件下,如何正确获得共享数据?举例:假设有多个用户需要获取用户信息,一个线程对应一个用户.在mybatis中,session用于操作数据库,那么设置.获取操作分别是session.set().session.get(),如何保证每个线程都能正确操作达到想要的结果? /** * 回顾sync

ThreadLocal介绍以及源码分析

ThreadLocal 线程主变量 前面部分引用其他优秀博客,后面源码自己分析的,如有冒犯请私聊我. 用Java语言开发的同学对 ThreadLocal 应该都不会陌生,这个类的使用场景很多,特别是在一些框架中经常用到,比如数据库事务操作,还有MVC框架中数据跨层传递.这里我们简要探讨下 ThreadLocal 的内部实现及可能存在的问题. 首先问自己一个问题,让自己实现一个这个的功能类的话怎么去做?第一反应就是简单构造一个 Map<Thread, T> 数据结构,key是 Thread,va

阿里架构师浅析ThreadLocal源码——黄金分割数的使用

一. 前提 最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量.问题是解决了,但是后来发现对ThreadLocal的理解不够深入,于是顺便把它的源码阅读理解了一遍.在谈到ThreadLocal之前先买个关子,先谈谈黄金分割数.本文在阅读ThreadLocal源码的时候是使用JDK8(1.8.0_181). 二. 黄金分割数与斐波那契数列 首先复习一下斐波那契数列,下面的推导过程来自某搜

Java中线程局部变量ThreadLocal使用教程及源码分析

在Java多线程编程中有时候会遇见线程本地局部变量ThreadLocal这个类,下面就来讲讲ThreadLocal的使用及源码分析. ThreadLocal 是Thread Local Varial(线程局部变量)的意思,每个线程在使用线程局部变量的时候都会为使用这个线程局部变量的线程提供一个线程局部变量的副本,使得每个线程都可以完全独立地操作这个线程局部变量,而不会与其他线程发生冲突,从线程的角度来看,每个线程都好像独立地拥有了这个线程局部变量.这样,看似每个线程都在并发访问同一个资源(线程局

ThreadLocal定义、使用案例及源码分析

原文连接:(http://www.studyshare.cn/blog-front//blog/details/1165/0 ) 一.ThreadLocal定义 jdk官方文档定义是:该类提供线程局部变量. 这些变量与其正常的对应方式不同,因为访问一个线程(通过其@code get或@code set方法)的每个线 程都有自己的独立初始化变量副本. 通俗来讲就是:使用ThreadLocal包装后的对象,在ThreadLocal所在线程中会有一个对象副本,该副本只会在拥有它的线程中使用,别的线程无