ThreadLocal分析和使用

Posted 陳英傑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal分析和使用相关的知识,希望对你有一定的参考价值。

ThreadLocal是一个多线程情况下为独立线程存储数据的类。
这样说可能不太好理解,下面通过一个例子来看清晰明了:

private ThreadLocal<Integer> local = new ThreadLocal<>();
private int i;


@RequiresApi(api = Build.VERSION_CODES.N)
private void threadLocal() 
    IntStream.range(0, 5).forEach(value -> new Thread(() -> 
        local.set(value);
        i = value;
        try 
            TimeUnit.SECONDS.sleep(2);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        System.out.println("thread: " + value + ", local: " + local.get() + ", i: " + i);
        // 使用完要回收,以免内存泄漏
        local.remove();
    ).start());

打印结果是:

thread: 0, local: 0, i: 4
thread: 1, local: 1, i: 4
thread: 2, local: 2, i: 4
thread: 4, local: 4, i: 4
thread: 3, local: 3, i: 4

这里我定义了一个普通int类型变量 i 和一个String类型ThreadLocal变量local,方法中创建了5个线程,线程编号就以value为主,在每个线程中分别为 i 和 local 赋值value,延迟2秒之后分别打印,从结果可以看到 i 的值是最后一次赋值的结果,而 local 是每个独立的线程赋值的结果,不受其他线程影响。

了解ThreadLocal的这种特性,主要从他的set、get方法来看:

先看看set方法

public class ThreadLocal<T> 

    public void set(T value) 
        // 获取此变量所在线程
        Thread t = Thread.currentThread();
                // 各个线程的值就存储变量在各个线程的值
        ThreadLocalMap map = getMap(t);
        // 如果map存在
        if (map != null)
            // 当前线程作为key,把value保存起来。
            map.set(this, value);
        else
            // 创建map
            createMap(t, value);
    

    ThreadLocalMap getMap(Thread t) 
        // 这个其就是ThreadLocal中的一个静态内部类
        return t.threadLocals;
    

    void createMap(Thread t, T firstValue) 
                // 这里正好是getMap方法的返回对象,初始化一个ThreadLocalMap,并以当前线程为key把value保存起来。
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    

class Thread implements Runnable 

        ...

    ThreadLocal.ThreadLocalMap threadLocals = null;

    ...

接下来我们来看看ThreadLocalMap:

public class ThreadLocal<T> 

    // ThreadLocalMap是ThreadLocal的静态内部类
    static class ThreadLocalMap         
    // ThreadLocalMap保存数据最终是在Entry中,key是当前线程,value是Object值(其实就是ThreadLocal泛型T的值)        
    	static class Entry extends WeakReference<ThreadLocal<?>> 
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) 
                super(k);
                value = v;
            
            
    

然后是get方法

public class ThreadLocal<T> 
    public T get() 
        // 获取当前所在线程
        Thread t = Thread.currentThread();
        // 得到map
        ThreadLocalMap map = getMap(t);
        if (map != null) 
            // 以当前线程为key,获取Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) 
                @SuppressWarnings("unchecked")
                // entry不为空,拿到value返回。
                T result = (T)e.value;
                return result;
            
        
        // 否则就初始化一个值
        return setInitialValue();
    

    // 初始化一个值(initialValue()方法返回null),并保存下来
    private T setInitialValue() 
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    

总结:
1、每一个Thread维护者一个ThreadLocalMap的引用,getMap方法提现;
2、Thread是通过ThreadLocal来操作变量的,创建的副本变量(vn)存储在每个线程的threadLocals中,也就是ThreadLocalMap;
3、ThreadLocal本身并不存储数据,它只是作为ThreadLocalMap存储数据的key;
注意内存泄漏问题:ThreadLocalMap中的Entry是持有ThreadLocal对象的弱引用(Entry的key),当发生GC时ThreadLocal会被回收,这时候key没有了,但是value还在(人s了,钱没花了,气人不),key不在了说明这个value不可能在被存取了,默默的躺在内存中没有一点用,所以在使用完及时调用remove方法避免内存泄漏。

ThreadLocal和 Synchronized区别:
ThreadLocal和Synchonized都能用于解决多线程并发访问时线程安全问题。
1、 Synchronized是通过锁机制,牺牲时间为代价,多线程间数据共享;
2、ThreadLocal是给每个线程分配单独的存储空间,牺牲空间为代价,多线程间数据独立(线程隔离);
当某些数据是以线程为作用域且不同线程具有不同的数据时可以使用ThreadLocal。

个人学习体会,欢迎探讨。

以上是关于ThreadLocal分析和使用的主要内容,如果未能解决你的问题,请参考以下文章

什么是 ThreadLocal 变量?

什么是 ThreadLocal 变量?

ThreadLocal 源码分析

ThreadLocal使用场景分析

ThreadLocal分析和使用

ThreadLocal使用和源码分析