ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

并发编程系列(五)ThreadLocal的应用与源码解析

2020-02-20 20:43:30  阅读:191  来源: 互联网

标签:thread 编程 value ThreadLocal 源码 线程 Entry null


ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间对一些公共变量的传递的复杂度。但是如果滥用ThreadLocal,就可能会导致内存泄漏。

ThreadLocal的应用

public class ThreadLocalDemo {
    private ThreadLocal<Integer> local = ThreadLocal.withInitial(() -> 3);
    /**
     * 信号量,许可数为1
     */
    private Semaphore semaphore = new Semaphore(1);
    private Random random = new Random();
    public class Work implements Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(random.nextInt(1000));
                //获取许可
                semaphore.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("thread name " + Thread.currentThread().getName() + " old value " + local.get());
            int value = random.nextInt();
            System.out.println("thread name " + Thread.currentThread().getName() + " set value " + value);
            local.set(value);
            System.out.println("thread name " + Thread.currentThread().getName() + " last value " + local.get());

            local.remove();
            semaphore.release();
        }
    }

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

输出结果:
thread name pool-1-thread-1 old value 3
thread name pool-1-thread-1 set value 45118831
thread name pool-1-thread-1 last value 45118831
thread name pool-1-thread-2 old value 3
thread name pool-1-thread-2 set value -944420704
thread name pool-1-thread-2 last value -944420704
thread name pool-1-thread-3 old value 3
thread name pool-1-thread-3 set value -1180451384
thread name pool-1-thread-3 last value -1180451384

结论:
虽然我们的ThreadLocal 变量在三个线程间是共享的,但是根据输出结果我们可以发现,虽然前一个线程重新设置了ThreadLocal的值,但是后一个线程在获取ThreadLocal中的值时,并没有获取到是上一个线程设置的值,而还是ThreadLocal的初始这。所以我们可以得出结论,ThreadLocal只在当前线程中起作用,在多个线程间并不共享。下面我们通过源码的类关系图可以发现,如下图
在这里插入图片描述

ThreadLocal源码解析

Thread类源码解析

public class Thread implements Runnable {
    private volatile String name;
    private int            priority;
    /* Whether or not the thread is a daemon thread. */
    private boolean     daemon = false;
    /* What will be run. */
    private Runnable target;
    /* The context ClassLoader for this thread */
    private ClassLoader contextClassLoader;
    
   // Thread类中的 threadLocals 属性
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ......
}

ThreadLocal的set方法

    /**
     *设置ThradLocal变量的值
     */
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThradLocalMap
        ThreadLocalMap map = getMap(t);
         //如果当前线程的ThreadLocalMap非空
        if (map != null)
            //往ThreadLcoal添加K-V
            map.set(this, value);
        else
            //如果创建当前线程的ThreadLocalMap为空
            //创建ThreadLocalMap对象
            createMap(t, value);
    }
	
	// 获取当前线程中的 threadLocals 属性
	ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
/**
 * ThreadLocalMap 存储数据
*/
private void set(ThreadLocal<?> key, Object value) {
        // hash表Entry数组
        Entry[] tab = table;
        // hash表Entry数组的⻓度
        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)]) {
            // 获取e的key
            ThreadLocal<?> k = e.get();
            // 如果k等于key,找到了对应的Entry
            // 更新Entry的值
            e.value = value;
            // 跳出循环
            return;
        }
        // 如果Entry的key为空
        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)
         // 重新调整hash表的⼤⼩
         rehash();
}

// 实例化Entry对象
static class Entry extends WeakReference<ThreadLocal<?>> {
   /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
    // 调用WeakReference 弱引用的构造方法
        super(k);
        value = v;
    }
}

ThreadLocal的get方法

  /**
     *返回当前线程副本中的值
     *线程局部变量。如果变量没有
     *当前线程,它首先初始化为返回的值
     *通过调用{@link#initialValue}方法。
     */
    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //调用getMap方法获取当前线程t中的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        //如果ThreadLocalMap对象非空,则进行写入操作
        if (map != null) {
            //ThreadLocalMap的结构为K-V的Map结构
            //ThreadLocalMap的内部类Entry(与HashMap类似)
            //key是ThreadLocal对象,Value是ThreadLocal对象的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            //Entry非空即当前这个ThreadLocal对象的K-V存在
            if (e != null) {
                @SuppressWarnings("unchecked")
                //ThreadLocal.Entry中的value的值
                T result = (T)e.value;
                return result;
            }
        }
        //如果ThreadLocalMap对象为空,则进行初始化
        return setInitialValue();
    }

通过查看ThreadLocal的源码我们发现,ThreadLocal底层是通过ThreadLocalMap类型实现的,而ThreadLocalMap是通过Entry[] table的数组实现的, "threadLocal.threadLocalHashCode & (table.length - 1) " 通过计算threadLocal的hash值&table的length 计算数组的下标;将threadLocal存入Entry的key中将value存入Entry的value中。
注意threadLocal存入Entry的key中是直接调用的WeakReference的构造方法,所以Entry的key值是存在弱引用的。

ThreadLocal的内存泄漏问题

如果我们在开发过程中,完全符合jdk提供的开发要求是不会造成内存泄漏问题的,但是由于开发人员的不当操作可能会在高并发环境下,导致使用ThreadLocal而发生内存泄漏问题。
通过上面的介绍,我们知道,Entry的key是弱引用的,也就是在发生GC时就有可能会被回收掉,将导致ThreadLocalMap的key变为null,从而导致ThreadLocalMap是一个无用的数据,但是还存在Thread -> ThreaLocalMap -> Entry -> value的强引用链,导致内存无法被回收,从而导致内存泄漏问题。所以我们需要在使用完ThreadLocal后,需要调用ThreadLocal.remove()方法

ThreadLocal的remove()方法

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

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

    // 将Entry的value 设为null
    tab[staleSlot].value = null;
	//将Entry 设为null
    tab[staleSlot] = null;
    size--;

    // 重置Entry数组
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == 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;
}

我们知道了,ThreadLocal的remove()方法是如何做的,是将Entry数组中ThreadLocal为null的,根据ThreadLocal计算Entry数组的下标,将此下标的Entry设为null。

二码先生 发布了14 篇原创文章 · 获赞 0 · 访问量 378 私信 关注

标签:thread,编程,value,ThreadLocal,源码,线程,Entry,null
来源: https://blog.csdn.net/fd135/article/details/104413402

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有