ThreadLocal笔记
Posted 做一个苦行僧
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal笔记相关的知识,希望对你有一定的参考价值。
在java中,变量之间共享可以使用public static变量的形式,所有的线程都使用同一个public static变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?这个时候就需要用到ThreadLocal了。类ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子可以存储每个线程的私有数据。
先来看一个数据隔离的例子:
public class Tools
public static ThreadLocal t1 = new ThreadLocal();
public class Test
public static void main(String []args)
try
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
for (int i=0;i<10;i++)
Tools.t1.set("Main "+i);
System.out.println("Main get value "+Tools.t1.get());
Thread.sleep(200);
catch (Exception e)
static class ThreadA extends Thread
@Override
public void run()
super.run();
try
for (int i=0;i<10;i++)
Tools.t1.set("ThreadA"+i);
System.out.println("ThreadA get value "+Tools.t1.get());
Thread.sleep(200);
catch (Exception e)
static class ThreadB extends Thread
@Override
public void run()
super.run();
try
for (int i=0;i<10;i++)
Tools.t1.set("ThreadB"+i);
System.out.println("ThreadB get value "+Tools.t1.get());
Thread.sleep(200);
catch (Exception e)
运行上面的代码,打印如下(打印有省略):
从打印的内容可以看出,我们的ThreadLocal类型的Tools.t1 实例,在ThreadA ThreadB 和main Thread中表现出数据隔离,每个人都有一份,互不干扰
ThreadLocal.java类的代码量不多:
看类接口,也就下面的以下属性和方法,我们这里主要关注的是set(T) ,get()方法。。
public void set(T value)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
ThreadLocalMap getMap(Thread t)
return t.threadLocals;
//Thread.java类中
ThreadLocal.ThreadLocalMap threadLocals = null;
在调用set方法设置一个值的时候,首先获取当前Thread对象,然后获取ThreadLocalMap对象。在ThreadLocal中其实是 ThreadLocal.ThreadLocalMap在存放值和 取值。调用set方法的时候,map.set(this,value),将当前对象ThreadLocal作为唯一key。但是在每一个线程对象中都存放有一个ThreadLocalMap类型的变量, 后面的createMap就是初始化ThreadLocalMap的
void createMap(Thread t, T firstValue)
t.threadLocals = new ThreadLocalMap(this, firstValue);
get()方法:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the @link #initialValue method.
*
* @return the current thread's value of this thread-local
*/
public T get()
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
return setInitialValue();
ThreadLocalMap getMap(Thread t)
return t.threadLocals;
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;
protected T initialValue()
return null;
关于get()方法的解读如下:
1.获取当前线程的ThreadLocalMap对象threadLocals
2.从map中获取线程存储的K-V Entry节点。
3.从Entry节点获取存储的Value副本值返回。
4.map为空的话返回初始值null,即线程变量副本为null,在使用时需要注意判断NullPointerException。
在get方法中,有可能会返回为null ,所以上面第4条也说了,要注意NullPointerException。我们还有另外一种可以预防null的方法,自定义ThreadLocal:
class ThreadLocalExt extends ThreadLocal
@Override
protected Object initialValue()
return xxxx;
继承ThreadLocal类,重写initialValue方法,返回默认值,这样在get的时候,如果获取不到值,就不会返回null了。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。
static class Entry extends WeakReference<ThreadLocal<?>>
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v)
super(k);
value = v;
和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置
private static int nextIndex(int i, int len)
return ((i + 1 < len) ? i + 1 : 0);
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len)
return ((i - 1 >= 0) ? i - 1 : len - 1);
显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。
所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。
ThreadLocalMap的问题
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何避免泄漏
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
ThreadLocal<String> threadLocal = new ThreadLocal<Session>();
try
threadLocal.set("hello World");
finally
threadLocal.remove();
InheritableThreadLocal
使用InheritableThreadLocal类可以在子线程中取得父线程继承下来的值。可以在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。
public static void main(String[] args)
final ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("帅的一匹");
Thread t = new Thread(new Runnable()
@Override
public void run()
Log.e("TAG","张三 帅么? " + threadLocal.get());
);
输出 “张三 帅么? 帅的一匹”,我们threadLocal是在main线程中定义的。而我们在子线程Thread中可以访问到。
使用InheritableThreadLocal 不仅可以值继承,而且还可以修改他的值。
public static void main(String[] args)
final ThreadLocal threadLocal = new InheritableThreadLocalExt();
threadLocal.set("帅的一匹");
Thread t = new Thread(new Runnable()
@Override
public void run()
Log.e("TAG","张三 帅么? " + threadLocal.get());
);
t.start();
static class InheritableThreadLocalExt extends InheritableThreadLocal
@Nullable
@Override
protected Object initialValue()
return "张三帅么?";
@Override
protected Object childValue(Object parentValue)
return parentValue+" 你确定么?";
自定义InheritableThreadLocalExt 可以重写childValue方法,childValue方法中参数parentValue就是 父线程中的值,也就是,上面代码的最后输出如下:
2020-08-15 22:32:25.674 31927-32194/com.android.recycleview.pingdemo E/TAG: 张三 帅么? 帅的一匹 你确定么?
可以通过childValue修改在子线程中通过InheritableThreadLocal.get()方法的返回值。
以上是关于ThreadLocal笔记的主要内容,如果未能解决你的问题,请参考以下文章