JavaSE-- Multithreading: ThreadLocal Comments

1. Concept

ThreadLocal used to 提供线程局部变量be in a multithreaded environment 保证各个线程里的变量独立于其它线程里的变量, that is to say ThreadLocal can create a separate copy of the variable for each thread, the thread is equivalent to private static type variable

Simple use of 2.ThreadLocal

public class Test {
    private static String commStr;
    private static ThreadLocal<String> threadStr = new ThreadLocal<String>();
    public static void main(String[] args) {
        commStr = "main";
        threadStr.set("main");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                commStr = "thread";
                threadStr.set("thread");
            }
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(commStr);
        System.out.println(threadStr.get());
    }
}
//thread
//main

As seen from the results of operation, for ThreadLocal type variable set value in a thread, which does not affect the value in the other thread, the value of a variable that is independent of the type ThreadLocal in each thread

3.ThreadLocal achieve

3.1.set (T value) method

  • set (T value) method, first obtains the current thread, then the current thread acquired ThreadLocalMap
  • If ThreadLocalMap is not null, then the value stored in the ThreadLocalMap, and with the current ThreadLocal as a key; otherwise, and to create a ThreadLocalMap to the current thread, and then save the value
  • ThreadLocalMap equivalent to a HashMap, is where the real value of saving

3.2.get () method

  • Will get to the current thread in the get () method ThreadLocalMap
  • If ThreadLocalMap is not null, put the key to get the value of the current ThreadLocal, or call setInitialValue () method returns the initial value, and save it to the newly created ThreadLocalMap

Detailed 3.3.ThreadLocalMap

In the set, get, initialValue and remove methods will get to the current thread, and then get to ThreadLocalMap by the current thread, if ThreadLocalMap is null, it will create a ThreadLocalMap, and give to the current thread

...
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
...

You can see, each thread will hold a ThreadLocalMap, used to maintain thread-local value:

public class Thread implements Runnable {
...
    ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

When using ThreadLocal variables related to the type of operation, the operation will be completed by the current thread to get ThreadLocalMap,每个线程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自己的。这就保证了ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响

3.3.1. Constructor

ThreadLocal in the current thread is null ThreadLocalMap use ThreadLocalMap constructor to create a new ThreadLocalMap:

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

Constructors will 新建一个数组, for the first time and will need to be saved to an array of key-value store, complete some initialization

3.3.2. Storage Structure

Internal ThreadLocalMap maintains a hash table (array) to store data, and defines the load factor:

// 初始容量,必须是 2 的幂
private static final int INITIAL_CAPACITY = 16;
// 存储数据的哈希表
private Entry[] table;
// table 中已存储的条目数
private int size = 0;
// 表示一个阈值,当 table 中存储的对象达到该值时就会扩容
private int threshold;
// 设置 threshold 的值
private void setThreshold(int len) {
threshold = len * 2 / 3;
}复制代码

Entry table is an array type, Entry of a ThreadLocalMap内部类

3.3.3. Storage Structure

Entry for storing a key-value pair, wherein the key storage weak reference in:

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

3.3.4. Save key for

Call set (ThreadLocal key, Object value) method to save the data to the hash table:

private void set(ThreadLocal key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        // 计算要存储的索引位置
        int i = key.threadLocalHashCode & (len-1);
        // 循环判断要存放的索引位置是否已经存在 Entry,若存在,进入循环体
        for (Entry e = tab[i];
        e != null;
        e = tab[i = nextIndex(i, len)]) {
            ThreadLocal k = e.get();
            // 若索引位置的 Entry 的 key 和要保存的 key 相等,则更新该 Entry 的值
            if (k == key) {
                e.value = value;
                return;
            }
            // 若索引位置的 Entry 的 key 为 null(key 已经被回收了),表示该位置的 Entry 已经无效,用要保存的键值替换该位置上的 Entry
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        // 要存放的索引位置没有 Entry,将当前键值作为一个 Entry 保存在该位置
        tab[i] = new Entry(key, value);
        // 增加 table 存储的条目数
        int sz = ++size;
        // 清除一些无效的条目并判断 table 中的条目数是否已经超出阈值
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash(); // 调整 table 的容量,并重新摆放 table 中的 Entry
}

First, using the key (the current ThreadLocal) calculating an index of position i threadLocalHashCode to be stored, by the value of threadLocalHashCode ThreadLocal class management, each ThreadLocal objects are created automatically generate a corresponding threadLocalHashCode a value, which achieve the following:

// ThreadLocal 对象的 HashCode
private final int threadLocalHashCode = nextHashCode();
// 使用 AtomicInteger 保证多线程环境下的同步
private static AtomicInteger nextHashCode = new AtomicInteger();
// 每次创建 ThreadLocal 对象是 HashCode 的增量
private static final int HASH_INCREMENT = 0x61c88647;
// 计算 ThreadLocal 对象的 HashCode
private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
}

Saving the data, if the index Location Entry, the Entry and the key is null, then it will perform cleanup operations Invalid Entry, as the Entry key using the 弱引用way, if the key is recovered (i.e. key is null), then you no longer have access to key corresponding to the value, need such invalid Entry removed to make room. When the capacity adjustment table will first clear the invalid objects, and then expansion as needed:

private void rehash() {
        // 先清除无效 Entry
        expungeStaleEntries();
        // 判断当前 table 中的条目数是否超出了阈值的 3/4
        if (size >= threshold - threshold / 4)
        resize();
}

3.3.5. Getting Entry objects

The values ​​are obtained directly Entry object using getEntry (ThreadLocal key) Method:

private Entry getEntry(ThreadLocal key) {
        // 使用指定的 key 的 HashCode 计算索引位置
        int i = key.threadLocalHashCode & (table.length - 1);
        // 获取当前位置的 Entry
        Entry e = table[i];
        // 如果 Entry 不为 null 且 Entry 的 key 和 指定的 key 相等,则返回该 Entry
        // 否则调用 getEntryAfterMiss(ThreadLocal key, int i, Entry e) 方法
        if (e != null && e.get() == key)
            return e;
        else 
            return getEntryAfterMiss(key, i, e);
}

Because there may be 哈希冲突, Entry key corresponding memory location may not be calculated by the index key position, i.e. the position Entry index is not necessarily the key corresponding to the Entry, it is necessary to call getEntryAfterMiss (ThreadLocal key, int i, Entry e) method to get:

private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
        // 索引位置上的 Entry 不为 null 进入循环,为 null 则返回 null
        while (e != null) {
            ThreadLocal k = e.get();
            // 如果 Entry 的 key 和指定的 key 相等,则返回该 Entry
             if (k == key)
                return e;
             // 如果 Entry 的 key 为 null (key 已经被回收了),清除无效的 Entry
            // 否则获取下一个位置的 Entry,循环判断
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
}

3.3.6. Remove the specified Entry

private void remove(ThreadLocal key) {
        Entry[] tab = table;
        int len = tab.length;
        // 使用指定的 key 的 HashCode 计算索引位置
        int i = key.threadLocalHashCode & (len-1);
        // 循环判断索引位置的 Entry 是否为 null
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            // 若 Entry 的 key 和指定的 key 相等,执行删除操作
            if (e.get() == key) {
                // 清除 Entry 的 key 的引用
                e.clear();
                // 清除无效的 Entry
                expungeStaleEntry(i);
                return;
            }
        }
}

3.4 Memory Leak

In ThreadLocalMap the set (), get () and remove () method, are invalid Entry clearance operations, in order to do so 降低内存泄漏发生的可能. Entry in a key way to use weak references, this is done in order to reduce the probability of occurrence of a memory leak, but 不能完全避免memory leaks.
The key assumptions Entry not use weak references, but the use of strong references: As ThreadLocalMap life cycle and as long as the current thread, then when the object reference ThreadLocal is recovered, due ThreadLocalMap also holds ThreadLocal and the corresponding value of strong references, ThreadLocal and the corresponding value will not be recovered, which causes a memory leak. Entry manner so weak references to avoid ThreadLocal not been recovered and lead to memory leaks, but this time the value is still not recovered, still cause a memory leak.
ThreadLocalMap we have considered this situation, and there are some precautions: 在调用ThreadLocal的get(),set()和 remove() 的时候都会清除当前线程 ThreadLocalMap 中所有 key 为null的value。这样可以降低内存泄漏发生 的概率,所以我们在使用ThreadLocal的时候,每次用完ThreadLocal都调用the Remove () method `clear data, prevent memory leaks.

Guess you like

Origin blog.csdn.net/LiLiLiLaLa/article/details/94407279