`

源码分析(七)——ThreadLocalMap(基于JDK1.6)

 
阅读更多

一、ThreadLocalMap的定义:

static class ThreadLocalMap 

ThreadLocalMap类是ThreadLocal类的一个内部类,关于ThreadLocal类可以查看上一篇。

 

 

二、ThreadLocalMap的成员变量:

ThreadLocalMap类有4个成员变量:1个常量、3个变量。

        /**
         * 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

 分别为:存放元素的数组、数组初始大小、数组元素的个数、阀值。

 

 

三、ThreadLocalMap的构造方法:

1. 第一个构造方法:带2个参数(默认的构造方法)。

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

        }

先对table进行初始化,默认大小为INITIAL_CAPACITY。然后计算第一对键值对要存放的位置,即在table中的下标,然后为这个键值对在数组相应的下标下创建一个Entry对象,最后设置阈值。在这里它的阈值为table长度的2/3。 

 

2. 第二个构造方法:带1个参数(私有构造方法)。

        /**

         * 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++;

                    }

                }

            }

 

        }

改私有的构造方法是由ThreadLocal里的createInheritedMap()中调用的,这个方法会由Thread里的构造方法来调用(在Thread类里的init()方法来调用,而init()方法只被Thread里的构造方法调用)。

 

 

四、ThreadLocalMap的set方法:

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

        /**
         * Replace a stale entry encountered during a set operation
         * with an entry for the specified key.  The value passed in
         * the value parameter is stored in the entry, whether or not
         * an entry already exists for the specified key.
         *
         * As a side effect, this method expunges all stale entries in the
         * "run" containing the stale entry.  (A run is a sequence of entries
         * between two null slots.)
         *
         * @param  key the key
         * @param  value the value to be associated with key
         * @param  staleSlot index of the first stale entry encountered while
         *         searching for key.
         */
        private void replaceStaleEntry(ThreadLocal key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

 在set()方法中,首先通过key(threadLocal)中的hashc值与table的总长度取模,然后根据取模后的值作为下标,找到table当中下标为此值的entry,判断该entry是否存在,如果存在,判断entry里的key与value,如果与当前要保存的key与value相同的话就不保存直接返回。如果entry里的key为null的话,就替换为当前要保存的key与value。否则就是碰撞的情况了,这时就调用nextIndex()方法计算下一个坐标。 

如果计算后的坐标获取到的entry为null,就new一个Entry对象并保存进去,然后调用cleanSomeSlots()对table进行清理,如果没有任何Entry被清理,并且表的size超过了阈值,就会调用rehash()方法。 

cleanSomeSlots()会调用expungeStaleEntry清理陈旧过时的Entry。rehash则会调用expungeStaleEntries()方法清理所有的陈旧的Entry,然后在size大于阈值的3/4时调用resize()方法进行扩容。

 

 

五、ThreadLocalMap的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);
        }

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

getEntry()方法通过计算出的下标从table中取出entry,如果取得的entry为null或它的key值不相等,就调用getEntryAfterMiss()方法,否则返回。 

而在getEntryAfterMiss()是当通过key与table的长度取模得到的下标取得entry后,entry里没有该key时所调用的。这时,如果获取的entry为null,即没有保存,就直接返回null,否则进入循环不,计算下一个坐标并获取对应的entry,并且当key相等时(表明找到了之前保存的值)返回entry,或是entry为null时退出循环,并返回null。 

 

 

六、ThreadLocalMap的remove方法:

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

 计算到下标后,如果取得的entry的key与ThreadLocal相同,就调用Entry的clear方法把弱引用设置为null,然后调用expungeStaleEntry对table进行清理。

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    ThreadLocal_ThreadLocal源码分析_

    在JDK 8之前,ThreadLocalMap的实现是一个静态内部类,它不是HashMap,而是基于Entry数组实现的。Entry继承自WeakReference,这意味着ThreadLocal对象如果不再被引用,可以被垃圾收集器回收,避免内存泄漏。然而,...

    java并发包源码分析(3)ThreadLocal

    ### 知识点七:测试用例 测试用例可以帮助我们更好地理解ThreadLocal的使用和潜在问题: - 创建多个线程,每个线程使用ThreadLocal变量,如果变量引用的是同一个共享对象,修改其中一个线程的变量值,其他线程是否...

    ThreadLocal_ThreadLocal源码分析_源码.zip

    同时,`ThreadLocalMap`的构造、扩容、收缩以及弱引用的处理等都是源码分析的重点。 总的来说,ThreadLocal是一个强大的工具,可以有效解决多线程环境中的数据隔离问题,但在使用过程中需要注意内存管理,防止潜在...

    ThreadLocal 线程本地变量 及 源码分析.rar_开发_设计

    以上是对ThreadLocal的简单介绍和源码分析,实际使用中还需要结合具体业务场景进行合理设计和优化。通过深入理解ThreadLocal的工作原理,可以帮助我们更好地利用这一工具,提高代码的并发性能和可维护性。

    JDK的ThreadLocal理解(一)使用和测试

    ThreadLocal的内部维护了一个ThreadLocalMap,它是基于弱引用的HashMap。每个线程都有自己的ThreadLocalMap,键是ThreadLocal实例,值是线程局部变量的副本。弱引用的设计使得当ThreadLocal实例不再被外部引用时,...

    ThreadLocalMap之getEntry+getEntryAfterMiss.pdf

    ThreadLocalMap是ThreadLocal的内部类,它实现了键值对的数据结构,为每个线程维护了一个线程局部变量的副本。 ThreadLocalMap的getEntry方法用于获取当前线程ThreadLocal变量对应的值。在ThreadLocalMap中,每个...

    Java并发编程学习之ThreadLocal源码详析

    Java并发编程学习之ThreadLocal源码详析 ThreadLocal是Java并发编程中的一种机制,用于解决多线程访问共享变量的问题。它可以使每个线程对共享变量的访问都是线程安全的,使得多线程编程变得更加简单。 ...

    ThreadLocal的简单理解.doc

    七、ThreadLocalMap 中的 hash 冲突是如何处理的 ThreadLocalMap 中的 hash 冲突是通过 ThreadLocal 对象的 hash 值来解决的。当我们创建一个 ThreadLocal 对象时,它的 hash 值将被计算出来,并被用来作为 ...

    ThreadLocal的原理,源码深度分析及使用.docx

    ThreadLocal 的原理、源码深度分析及使用 ThreadLocal 是 Java 语言中的一种机制,用于实现线程本地存储,能够帮助开发者在多线程环境下解决变量访问安全的问题。下面将对 ThreadLocal 的原理、实现机制、使用方法...

    javalruleetcode-dsal:数据结构与算法个人整理

    基于链表实现队列、基于循环数组实现队列 : 栈 : 堆 : 集合与映射 :Hash 表 树形结构: : 二分查找树 : 平衡树 : 红黑树 : 线段树 : 字典树 其他: : 图 : 跳表 : 并查集 JDK 集合源码 基本 List, Set, ...

    java中ThreadLocal详解

    然后,通过当前线程获取对应的`ThreadLocalMap`实例,并基于`ThreadLocal`对象作为键查找对应的值。 2. **设置线程局部变量**:设置线程局部变量的过程类似。首先,获取当前线程和对应的`ThreadLocalMap`。如果当前...

    ThreadLocal分析

    在分析ThreadLocal源码时,可以了解到它如何在内部实现线程隔离,以及ThreadLocalMap的结构和工作方式。理解这些细节有助于我们更好地利用ThreadLocal,同时避免可能出现的问题。通过阅读源码,我们可以发现...

    一线大厂Java多线程面试120题.pdf

    掌握这些知识点并结合源码分析、性能优化和实际应用,能够显著提高面试者在一线大厂Java多线程面试中的竞争力,有助于提升职业发展。建议学习者按照课程大纲的顺序,结合实践深入理解,避免仅仅死记硬背题目。

    深入理解 Java 之 ThreadLocal 工作原理1

    ThreadLocalMap的实现使用了弱引用(WeakReference&lt;ThreadLocal&lt;?&gt;&gt;)作为key,这是因为即使没有外部引用指向ThreadLocal实例,只要它还在ThreadLocalMap中作为key存在,垃圾收集器就不会回收ThreadLocal实例。...

    day18 10.使用ThreadLocal来解决问题

    **源码分析** ThreadLocal的核心方法是`get()`、`set()`和`remove()`。`get()`方法首先查找当前线程的ThreadLocalMap,然后返回与当前ThreadLocal实例关联的值。`set()`方法会在ThreadLocalMap中创建一个新的Entry...

    美团面试,问了ThreadLocal原理,这个回答让我通过了

    `ThreadLocalMap`中的每个条目是一个`Entry`对象,它不是`HashMap`中的`Entry`,而是专为`ThreadLocalMap`设计的。`Entry`的key是`ThreadLocal`实例,value是用户存储的对象。`Entry`使用弱引用作为key,这样在`...

    Java ThreadLocal用法实例详解

    ThreadLocalMap的实现基于openjdk11的源码,下面是相关的源码分析: 1. get方法:ThreadLocal的get方法会首先检查当前线程是否已经设置了该线程局部变量,如果已经设置了则直接返回,如果没有设置则调用setInitial...

    2024最新阿里、京东、蚂蚁等大厂面试题

    - **实现原理**:通过ThreadLocalMap存储线程局部变量,使用Thread对象的ThreadLocalMap属性来实现线程隔离。 #### JVM中的共享区与GC Root - **共享区**:如方法区、字符串常量池等。 - **GC Root**:包括静态变量...

    java的ThreadLocal[整理].pdf

    当ThreadLocal的get()或set()方法被调用时,会检查当前线程的ThreadLocalMap,如果不存在,就会创建一个新的ThreadLocalMap。 下面是一个简单的ThreadLocal使用示例: ```java public class ThreadLocalSample { ...

Global site tag (gtag.js) - Google Analytics