深入理解多线程:ThreadLocal

27 篇文章 0 订阅

ThreadLoacal:线程内局部变量,对于一个相同的代码模块,每个线程访问的时候代码模块内的变量互不干扰,互不影响。相当于为每个线程创建一个单独变量的副本,相当于private static类型变量。在多线程中保证各个线程里的变量独立于其他线程的变量。
目的是解决变量在单个线程内部变量的传递问题,ThreadLocal修饰的变量多个线程之间不共享,不存在安全性问题,所以它并不是解决多线程之间的安全问题
ThreadLocal的作用和同步机制有些相反:同步机制是为了保证多线程下数据的一致性,ThreadLocal保证了多线程条件下数据的独立性。
ThreadLocal提供了线程本地变量,可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而同一个线程在任何情况下访问这个变量的结果都是一致的,当这个线程结束生命周期时,所有的线程的本地实例都会被GC,ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定;作用域是线程内部,伴随线程执行始终,线程结束,变量生命结束。
源码分析:
核心是通过静态内部类ThreadLocalMap来处理数据

static class ThreadLocalMap {
//entry是ThreadLocalMap核心存储kv键值对的,继承WeakReference,entry所对应的key(也就是ThreadLocal的实例)的引用是弱引用。
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

get():返回此线程局部变量中当前线程副本中的值
获取当前线程的ThreadLocalMap,如果map不为空,就将获得key值作为当前ThreadLocal的值,否则就调用setInitialValue方法返回初始值,并保存到新的ThreadLocalMap中。

public T get() {
//获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap的对象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        //通过key获取Entry实体
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
            //SuppressWarnings作用是给编译器一条指令,告诉他被批注的代码元素内部的某些警告保持静默。
            //在这里是一直没有权限访问的域的警告。
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果map为空就创建一个新Map并返回它(也就是空值)
        return setInitialValue();
    }

set(T value):设置当前线程的局部变量值
ThreadLocalMap相当于一个HashMap,是真正保存值的地方。

public void set(T value) {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       //如果map不为空就将当前的key和value更新到map中,用当前的ThreadLocal作为key,
       //否则就创建一个Map并给到当前线程,并把当前线程作为key值,和value保存到map中。
       if (map != null)
           map.set(this, value);
       else
           createMap(t, value);
   }

remove():将当前线程变量的值删除
目的是减少内存的占用,不需要显示调用此方法,线程结束后,对应的局部变量会自动回收。

每个线程都会持有一个ThreadLocalMap,用来维护线程本地的值。
在使用ThreadLocal变量进行操作时,都会通过当前线程获取到ThreadLocalMap来完成操作,每个线程的ThreadLocalMap都是自己的,ThreadLocalMap维护的值也是自己的,这就保证了线程变量的独立性。

ThreadLocalMap
构造方法(有两个构造方法):当前ThreadLocalMap为null时会使用它来构建一个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);
        }

存储结构:内部维护了一个哈希表(数组)来存储数据

private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
//阈值默认为0
private int threshold;
private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

table是一个entry类型的数组,Entry是ThreadLocalMap的一个内部类
保存键值对:调用set(ThreadLocal key,Object value)方法将数据保存到哈希表中
首先使用key值也就是当前ThreadLocal的threadLocalHashCode来计算要存储的索引位置i,每创建一个ThreadLocal对象都会自动生成一个threadLocalHashCode值。

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值相等,更新Entry的值
                if (k == key) {
                    e.value = value;
                    return;
                }
                //如果索引位置的Entry为null,就说明这个Entry已经无效,用要保存的键值对替换他
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //循环结束了也没有找到就将要保存的键值对作为一个新的Entry保存在该位置
            tab[i] = new Entry(key, value);
            //将数组的长度增加
            int sz = ++size;
            //清除一些无效的条目并且判断table中的元素是否已经超过了阈值
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

在保存数据的时候,如果索引位置有Entry并且Entry的key值为null,那么就会执行清除无效的Entry的操作,因为Entry的key使用的是弱引用方式,key如果被回收,就无法访问到key对应的value,所以这种Entry无效,将他们清除掉来腾出空间。

在整理table容量的时候也要清除无效对象,然后再根据需要扩容

private void rehash() {
           expungeStaleEntries();

           // Use lower threshold for doubling to avoid hysteresis
           if (size >= threshold - threshold / 4)
               resize();
       }

获取Entry对象

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
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

可能存在哈希冲突,key对应的Entry的存储位置可能不在通过key值计算出来的索引位置上,就要通过getEntryAfterMiss方法来获取。解决哈希冲突的方法是rehash

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

移除指定的Entry

private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            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) {
                //清除key的引用
                    e.clear();
                    //清除无效Entry
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

内存泄漏
ThreadLocalMap的set()、get()和remove()方法中都有清除无效Entry的操作,这样做是为了降低内存泄漏的可能性。
Entry中的key使用了弱引用的方式,这样做就是为了减少内存泄漏,但并不能完全避免内存泄漏。

如果Entry中的key使用的是强引用,由于ThreadLocalMap的生命周期和当前线程一样长,那么当引用ThreadLocal的对象回收以后,由于ThreadLocalMap还持有ThreadLocal和对应的value的强引用,ThreadLocal和对应的value是不会被回收的,这就导致了内存泄漏,但是此时的value还是无法被回收的,还是会导致内存泄漏。
ThreadLocalMap就考虑到了这个情况,并且在这些方法中进修表格清除无效Entry操作,所以我们在使用ThreadLocal都调用remove()方法清除数据,就能防止内存泄漏。

ThreadLocal使用场景
数据库链接和session管理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值