ThreadLocal,看我就够了!

简介: ThreadLocal开胃菜 研究过Handler的应该对ThreadLocal比较眼熟的,线程中的Handler对象就是通过ThreadLocal来存放的。

ThreadLocal


开胃菜

 研究过Handler的应该对ThreadLocal比较眼熟的,线程中的Handler对象就是通过ThreadLocal来存放的。初识ThreadLocal的可能被它的名字有所误导,ThreadLocal初一看可能会觉得这是某种线程实现,而实际并非如此。事实上,它是一个全局变量,用来存储对应Thread的本地变量,这也是为什么将其称之为Local。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
 比如Handler,当我们在一个线程中创建了一个Handler时,在调用Looper.prepare()时通过ThreadLocal保存了当前线程下的Looper对象,而所有线程的Looper都由一个ThreadLocal来维护,也就是在所有线程中创建的Looper都存放在了一个ThreadLocal中,然后创建Handler将Handler与当前线程Looper关联,当调用Looper.loop()的时候通过myLooper()得到的就是当前线程的Looper,当在其他线程使用Handler来发送消息的时候,其实也就是将对应的Message存储到了对应Handler的MessageQueue中,当Looper去分发消息的时候,就是将当前线程中的Looper对应的MessageQueue中的Message通过Handler的回调返回给了Handler所在的线程。如对Handler不了解的,可参考Handler全面解读

img_206bc3a097999686b58ad462a01a7531.png
threadlocal_handler.png


ThreadLocal模拟

 那么,如何来做到区分不同线程中的变量呢?我们这里模拟一个实现ThreadLocal功能的类,原理大致一样。

public class ThrealLocalImitation<T> {
    private static Map<Thread, Object> sSaveValues = new HashMap<Thread, Object>();

    public synchronized void set(T threadData) {
        Thread thread = Thread.currentThread();
        mSaveValues.put(thread, threadData);
    }

    public synchronized T get() {
        Thread thread = Thread.currentThread();
        return (T) mSaveValues.get(thread);
    }
}

 如上ThreadLocal模仿类里面,通过全局的Map变量sSaveValues,以Thread为key,value为对应线程需要保存的变量,实现了,每个线程对应保存了一个变量,在不同的线程存储不同的变量,通过get方法就能取回对应的值。


ThreadLocal原理分析

 ThreadLocal类通过set、get方法来分别存取变量的,搞懂了这两个方法的功能也就明白ThreadLocal的原理了,所以重点分析这两个方法。

在进入正式的分析之前先来看一个类——ThreadLocalMap

 和我们模拟的ThreadLocal稍有所区别,ThreadLocal不是直接通过一个Map来存储Thread和value对应关系的。
 在Thread类中,有一个变量ThreadLocal.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);
        }

    static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

 ThreadLocalMap内部维护一个数组Entry[] table, Entry对应存储了key--value(ThreadLocal---value)。ThreadLocalMap实际上是一个实现了自定义的寻址方式的HashMap。
 那么ThreadLocal是如何存储线程本地变量的呢?先给个简单的结论。
每个Thread在生命周期中都会维护着一个ThreadLocalMap,可以看成是一个存储了ThreadLocal(key)---value的HashMap,当ThreadLocal存储value时,先通过当前Thread得到其维护的ThreadLocalMap,然后将其存储到该map中,而获取value时则是先获取到当前线程的ThreadLocalMap,然后通过当前的ThreadLocal,获取到ThreadLocalMap存储的value值。

set()方法

public void set(T value) {
    Thread t = Thread.currentThread();//获取到当前线程
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

 set方法中,首先通过Thread.currentThread()获取到当前的线程,通过当前线程获得其维护的ThreadLocalMap,当map为空时,则为当前Thread创建一个ThreadLocalMap,不为空的话则将ThreadLocal--value存储到map中。
所以一个Thread对应着一个ThreadLocalMap,而一个ThreadLocalMap对应着多个ThreadLocal。

get()方法

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

 get()方法,同样是先获取到当前Thread,然后获取到当前Thread的ThreadLocalMap,然后根据ThreadLocal自身,通过ThreadLocalMap自身的寻址方式获取到存储ThreadLocal和value的Entry对象,进而得到value。
 setInitialValue();是当ThreadLocalMap为空时,可以通过实现ThreadLocal的initialValue()来获得一个默认值,同时该默认值会被存储到线程的ThreadLocalMap中。


内存泄漏

 分析到这里,ThreadLocal的原理已经很明朗了。但是一些使用不当的情况出现内存泄漏的风险,所以最后讲解下ThreadLocal会出现的内存泄漏风险,及如何避免。
 ThreadLocalMap中存储的Entry为ThreadLocal--value,准确的描述应该是weakReference(ThreadLocal)--value,即,key(ThreadLocal为弱引用),而value则是强引用的,当ThreadLocal为空后,Thread不会再持有ThreadLocal引用,ThreadLocal可以被GC回收,但是Thread的ThreadLocalMap仍然还持有value的强引用,导致value需要等待线程生命周期结束才可能被GC回收。当出现一些长时间存在的线程,不断的存储了内存比较大的value,而value实际是不再被使用的,value由于线程没有被回收而不断的堆积,造成了内存泄漏。比如当使用到线程池是,Thread很有可能不会被马上结束,可能会被不断的重复利用。
 所以这里引入ThreadLocal的另外一个方法——remove方法

remove()方法

    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }


    //ThreadLocalMap中的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;
                }
            }
        }

 所以在value不再使用时,应该及时调用remove,解除线程对该value的引用。

目录
相关文章
|
7月前
|
存储 安全 Java
【ThreadLocal】
【ThreadLocal】
|
28天前
|
存储 Java 数据管理
ThreadLocal的使用
`ThreadLocal`是Java中的线程局部变量工具,确保每个线程都有自己的变量副本,互不干扰。适用于保持线程安全性数据和跨方法共享数据。基本用法包括创建实例、设置和获取值以及清除值。例如,创建ThreadLocal对象后,使用`.set()`设置值,`.get()`获取值,`.remove()`清除值。注意ThreadLocal可能引起内存泄漏,应适时清理,并谨慎使用以避免影响代码可读性和线程安全性。它是多线程编程中实现线程局部数据管理的有效手段。
46 10
|
4月前
|
存储 Java
ThreadLocal 有什么用
ThreadLocal 有什么用
21 0
|
8月前
|
存储 安全 Java
ThreadLocal介绍和应用
ThreadLocal介绍和应用
25 0
|
10月前
|
缓存 安全 Java
浅谈ThreadLocal
浅谈ThreadLocal
110 0
|
11月前
|
存储 SQL Java
ThreadLocal的其他应用
request对象跟PageHelper
85 0
|
12月前
|
存储 Java 数据库连接
对ThreadLocal的一点了解
ThreadLocal是线程变量,它为每个线程提供单独的存储空间。其主要作用是做线程间的数据隔离,也可以用于在同一个线程间方便地进行数据共享。(对于多线程资源共享,加锁机制采用“时间换空间”,ThreadLocal采用“空间换时间”)
104 0
|
存储 Java
对threadlocal了解多少?
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK 中提供的 ThreadLocal 类正是为了解决这样的问题。 ThreadLocal 类主要解决的就是让每个线程绑定自己的值,可以将 ThreadLocal 类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
|
存储 分布式计算 安全
什么是ThreadLocal?
这篇文章是慕课网上一门免费课程《ThreadLocal》的观后总结。这门课将ThreadLocal讲得非常清晰易懂,又深入底层原理和设计思想,是我看过的最好的ThreadLocal的资料,现在把用自己的话,把它整理成文字版本。 总共预计产出四篇文章,这是第一篇。
229 3
|
Java 应用服务中间件
我把 ThreadLocal 能问的,都写了(下)
我把 ThreadLocal 能问的,都写了(下)
我把 ThreadLocal 能问的,都写了(下)