Java基础(十四):ThreadLocal使用及源码分析

ThreadLocal:线程本地变量副本。

当使用ThreadLocal维护变量时,会为每个使用该变量的线程提供独立的变量副本。

每一个线程都可以独立地改变自己的副本,而不会影响其他线程对应的副本。

public class ThreadLocalDemo {
    //初始化变量
    private ThreadLocal threadLocal = ThreadLocal.withInitial(()->3);

    private Random random = new Random();

    class Work implements Runnable{

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            int oldValue = (int) threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"threadLocal old value"+ oldValue);
            int newValue = random.nextInt(100);
            threadLocal.set(newValue);
            System.out.println(Thread.currentThread().getName()+"threadLocal new value"+ newValue);
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        ThreadLocalDemo threadLocalDemo= new ThreadLocalDemo();
        executorService.execute(threadLocalDemo.new Work());
        executorService.execute(threadLocalDemo.new Work());
        executorService.execute(threadLocalDemo.new Work());
        executorService.shutdown();
    }
}

输出结果:
pool-1-thread-3threadLocal old value3
pool-1-thread-1threadLocal old value3
pool-1-thread-2threadLocal old value3
pool-1-thread-1threadLocal new value90
pool-1-thread-3threadLocal new value89
pool-1-thread-2threadLocal new value30

在使用时,ThreadLocal线程安全。

源码详解

    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取线程t内的ThreadLocalMap对象,结构的KV的map结构,维护一个内部类Entry(类似HashMap)
        //key是threadLocal对象的引用,value是threadLocal对象的值。
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                //不为空,返回ThreadLocalMap.Entry中的值
                T result = (T)e.value;
                return result;
            }
        }
        //对象为空,进行初始化
        return setInitialValue();
    }
    
    //返回当前线程对象内的threadLocals属性,即ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    //获取对应的Entry
    private Entry getEntry(ThreadLocal<?> key) {
        //key的hashCode & 1111 (默认16) 即保留key的HashCode的低四位
        int i = key.threadLocalHashCode & (table.length - 1);
        //获取hash表位置i处的Entry对象
        Entry e = table[i];
        //e非空,且e的key等于key,说明就是要找的对象,返回
        //e.get()为弱引用
        if (e != null && e.get() == key)
            return e;
        else
            //继续找
            return getEntryAfterMiss(key, i, e);
    }
    
    //在hash表对应的位置上没有找到对应的Entry
    //采用线性探索法,继续在hash表上找
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
        
        while (e != null) {
            ThreadLocal<?> k = e.get();
            //key相等,直接返回
            if (k == key)
                return e;
            if (k == null)
                expungeStaleEntry(i);//k为null(可能被回收了),清理失效的key
            else
                i = nextIndex(i, len);//i变更为hash表上下一个位置
            e = tab[i];
        }
        return null;
    }


    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;

        // 清理staleSlot位置的Entry
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;

        // 调整hash表直到后⾯第⼀个tab[i]为null为⽌
        Entry e;
        int i;
        for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            // 如果key为null,将该entry置为null
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            } else {
                // 下⾯是新计算⼀下hash值,如果位置与当前位置不同
                // 需要重新找⼀个位置放该节点。⽤的也是线性探测法
                int h = k.threadLocalHashCode & (len - 1);
                if (h != i) {
                    tab[i] = null;
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    tab[h] = e;
                }
            }
        }
        return i;
    }    
    

    //i变更为hash表上下一个位置
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }



    //设置初始化值
    private T setInitialValue() {
        //默认为null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    //初始化线程中的ThreadLocalMap
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    //构造方法
    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);
    }
    //设置变量
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        //map不为空直接set,不为空,创建map并初始化值
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        // key的hashcode & 1111,即保留key的hashcode的低4位
        int i = key.threadLocalHashCode & (len-1);
        // 如果hash表Entry数组第i个位置的Entry⾮空,继续向后探索
        // 因为是通过线性探索法处理hash碰撞的
        for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            if (k == key) {
                // 更新Entry的值
                e.value = value;
                return;
            }
            if (k == null) {
                // 需要替换旧的Entry
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        // 如果hash表i位置没有Entry,直接创建新的Entry并存⼊位置i
        tab[i] = new Entry(key, value);
         //hash表⻓度加1
        int sz = ++size;
        // 如果没有可以清除的Entry并且hash表的⻓度⼤于等于扩容阈值
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    //替换失效的Entry
    //副作⽤是清除两个空的位置间的就的Entry对象
    private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // 备份以检查当前运⾏中的先前失效的Entry
            // 待清除的失效的Entry得位置
            int slotToExpunge = staleSlot;
            // 从staleSlot位置向前搜索,找到hash表第⼀个Entry为空的位置
            for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // 从staleSlot位置向后搜索
            // 找到hash表第⼀个Entry为空的位置,结束循环
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // 互换tab[i]和entry的位置
                // 因为ThreadLocalMap是根据线性探测法来解决冲突的
                // 因此可能会出现key的哈希值相同但散落位置不连续的情况
                // 为了在⼀定程度上提⾼查找哈希值相同entry节点的效率
                // 交换⼀下位置会是更好的选择
                if (k == key) {
                    e.value = value;

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

                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    执⾏清理⼯作
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

           // 如果没有找到key对应的Entry,则创建新的Entry并放⼊staleSlot位置
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // 如果还有其他的失效的Entry将其清除
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
    /**
     * 清理⾮null但是key为null的Entry 
     * 参数n决定了for循环要执⾏的次数。
     * 如果每次循环tab[i]均不需要清理,最多会执⾏logn次。
     * 如果有需要清理的节点,就会调⽤expungeStaleEntry()⽅法清理
     */
    private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

ThreadLocal内存泄漏问题

在线程池中,线程执行完后不被回收,而是返回线程池中。

Thread有个强引用指向ThreadLocalMap,ThreadLocalMap有强引用指向Entry,Entry的key是弱引用是ThreadLocal对象。

如果ThreadLocal使用一次后就不在有任何引用指向它,GC会将ThreadLocal对象回收掉。导致Entry变为{null:value}。此时这个Entry已经无效了,因为key被回收了,而value无法被回收,一直存在内存中。

结论:在执行了ThreadLocal.set()方法之后,一定要记得使用ThreadLocal.remove(),将不要的数据移除掉,避免内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值