zoukankan      html  css  js  c++  java
  • ThreadLocal原理分析

    一、概述

    ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。

    二、实现原理

    ThreadLocal相关UML类图:

     由该图可知,Thread类中有threadLocals和inheritableThreadLocals两个ThreadLocal.ThreadLocalMap类型的变量,二ThreadLocal.ThreadLocalMap是一个定制化的HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或get方法是才会创建它们。其实每个线程的本地变量不是存在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面。也就是说ThreadLocal类型的本地变量存放在具体的线程内存空间中。ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面并存放起来。当调用线程调用它的get方法是,再从当前线程中的threadLocals变量里面将其拿出来使用。如果调用线程一直不终止,name这个本地变量会一直存放在调用线程的threadLocals变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。另外,Thread里面的threadLocals为何设计为map结构,是因为每个线程可以存储多个ThreadLocal变量。

    1、set方法:

    public void set(T value) {
            Thread t = Thread.currentThread();//获取当前线程
            ThreadLocalMap map = getMap(t);//以当前线程为key,获取当前线程变量
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);//初始化线程threadLocals
        }
    //    
    ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    //    
    void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);//创建ThreadLocalMap对象(以当前ThreadLocal对象为key(firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)),firstValue为value)初始化;赋值给当前线程
        }

     2、get方法

    public T get() {
            Thread t = Thread.currentThread();//获取当前线程
            ThreadLocalMap map = getMap(t);//获取当前线程变量
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);//Hashmap取值
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);//计算数组坐标
                Entry e = table[i];
                if (e != null && e.get() == key)//计算key值
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }

    3、remove方法

    public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    
    //ThreadLocalMap移除变量   
    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;
                    }
                }
            }

     ThreadLocal不支持继承性,有关更多内容可以查看我的文章《Java线程变量问题-ThreadLocal》,地址:https://www.cnblogs.com/wangymd/p/11012658.html

    三、ThreadLocal内存泄露问题

    ThreadLocalMap使用ThreadLocal的弱引用未key,而value是强引用。如果ThreadLocal没有被外部强引用的情况下,在垃圾回收时,key会被清理掉,而value不会被清理掉。这样一来,ThreadLocalMap中就会出现key为null的Entry。如果不做处理的话,value永远无法被GC回收,此时就可能会产生内存泄露。虽然ThreadLocalMap实现中已经考虑了这种情况,在调用set()、get()、remove()方法的时候,会清理掉key为null的记录,但是建议使用完ThreadLocal方法后手动调用remove()方法。

    private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)//查询到Entry
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);//未查询到Entry
            }
            
    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)//key为null情况
                        expungeStaleEntry(i);
                    else
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }
            private int expungeStaleEntry(int staleSlot) {
                Entry[] tab = table;
                int len = tab.length;
    
                // expunge entry at staleSlot
                tab[staleSlot].value = null;
                tab[staleSlot] = null;
                size--;
    
                // Rehash until we encounter null
                Entry e;
                int i;
                for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {//key为null,删除线程变量值
                        e.value = null;
                        tab[i] = null;
                        size--;
                    } else {
                        int h = k.threadLocalHashCode & (len - 1);
                        if (h != i) {
                            tab[i] = null;
    
                            // Unlike Knuth 6.4 Algorithm R, we must scan until
                            // null because multiple entries could have been stale.
                            while (tab[h] != null)
                                h = nextIndex(h, len);
                            tab[h] = e;
                        }
                    }
                }
                return i;
            }
  • 相关阅读:
    docker 镜像相关
    docker相关网站
    docker初识 一
    loadrunner Windows资源指标
    Codeforces Round #368 (Div. 2) Brain's Photos
    CodeForce 589J Cleaner Robot
    CodeForce 677I Lottery
    CodeForce 677D Boulevard
    CodeForce 589B Layer Cake
    Map的遍历
  • 原文地址:https://www.cnblogs.com/wangymd/p/12980915.html
Copyright © 2011-2022 走看看