详解ThreadLocal使用及原理

先考虑两个问题:

  1. 是否了解ThreadLocal的使用?

  2. 是否了解ThreadLocal的原理?

1、首先,我们先简单介绍一下ThreadLocal的定义与使用。

ThreadLocal,线程本地变量,顾名思义,它是每个线程私有的本地变量。通俗点讲,当你创建了一个ThreadLocal变量,每个线程在访问该变量时,都会拷贝一个副本至本地内存,所以多线程下操作ThreadLocal变量时,其实各自都是在操作自己拷贝的副本,互不影响,这样自然而然就避免了线程安全问题。看个例子,可能就明白了。

public class ThreadLocalTest {

   //定义一个ThreadLocal的变量,泛型指定为Integer  

private ThreadLocal threadLocal = new ThreadLocal() {      

@Override        

protected Integer initialValue() {            

//初始值置为2          

return 2;      

 } };

@Test    

public void testThreadLocal() {      

//创建线程thread01        

MyThread thread01 = new MyThread("thread01");        

//启动线程thread01      

 thread01.start();

//创建线程thread02      

MyThread thread02 = new MyThread("thread02");        

//启动线程thread02        

thread02.start(); }

 class MyThread extends Thread {

       MyThread(String name) {    

super(name);        }

@Override        

public void run() {            

super.run();          

//获取threadLocal的值,并输出结果          

 System.out.println(getName() + "------ threadLocalValue is " + threadLocal.get());    //threadLocal的值 + 1,并设置到threadLocal            threadLocal.set(threadLocal.get() + 1);            

//再次获取threadLocal的值,并输出结果            

System.out.println(getName() + "-------after +++, threadLocalValue is " + threadLocal.get());        }    }}

// logcat输出如下////

thread1------ threadLocalValue is 1// thread1------ after +++, threadLocalValue is 2// thread2------ threadLocalValue is 1// thread2------ after +++, threadLocalValue is 2

例子很简单,开启两个线程去操作ThreadLocal变量,从控制台的输出结果,便可以证明我们上面说的定义,每个线程对threadLocal变量的访问与操作互不影响,做到了线程隔离。

2、作为程序员,咱是有追求的。必须得看看原理呀,点进源码,看看ThreadLocal的实现原理到底是怎么一回事?

  • 先从Thread类看起。

class Thread implements Runnable {    ... // 省略不相关代码    // 每个线程内部有个ThreadLocalMap的私有成员变量    ThreadLocal.ThreadLocalMap threadLocals = null;       ...// 省略不相关代码    private void exit() {        ...        //线程退出时, threadLocals变量置空         threadLocals = null;        ... //     }  }

整个Thread类中只需要关注两点就行,第一点,每个Thread都会有一个ThreadLocalMap类型的threadLocals变量,第二点就是这个threadLocals会在线程退出时置空。至于啥时候往里面放东西,先不用急,后面自然会提到。

  • 看看ThreadLocal的内部类ThreadLocalMap吧。

//ThreadLocalMap功能类似HashMap,内部维护了一个Entry数组进行存储数据static class ThreadLocalMap {        

//Entry是K-V形式的实体,key为ThreadLcal变量,value为任意类型的变量        

static class Entry extends WeakReference<ThreadLocal> {            //该value与ThreadLocal变量绑定            

Object value;

Entry(ThreadLocal k, Object v) {              

super(k);                

value = v;            }        }        

// 内部维护的Entry数组        

private Entry[] table;

// 构造方法        

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {          

 ... // 省略不相关代码            

// 初始化Entry数组            

table = new Entry[INITIAL_CAPACITY];            

... // 省略不相关代码        }}

ThreadLocalMap其实就是一个数据存储结构,类似HashMap。它内部也是维护了一个Entry实体数组,这个Entry是以K-V形式进行存储数据,key为ThreadLocal变量,value支持任意类型的变量,目前还不知道它具体存了什么,接着再往下看。

  • ThreadLocal本身终于要亮相了,看看ThreadLocal的核心方法setInitialValue / get / set 都做了啥。

public class ThreadLocal{   //设置初始值   private T setInitialValue() {        // 通过我们重写initialValue方法获取初始值        T value = initialValue();         // 获取到当前线程        Thread t = Thread.currentThread();         //通过getMap方法,传入当前线程作为参数去获取ThreadLocalMap对象        ThreadLocalMap map = getMap(t);         //如果获取到的ThreadLocalMap不为空,就直接存入,否则就创建ThreadLocalMap并存入。        if (map != null)            //key为threadLocal本身,value就是我们业务上需要存储的值            map.set(this, value);        else            createMap(t, value);        return value;    }  //获取ThreadLocalMap对象  ThreadLocalMap getMap(Thread t) {        //重点!!!,敲黑板啦!!!        //这里返回的,其实就是最上面讲到的每个Thread都会有一个ThreadLocalMap成员变量        return t.threadLocals;  }  //获取ThreadLocal的value值  public T get() {        //获取当前线程        Thread t = Thread.currentThread();        //跟上面一样,拿取当前线程的ThreadLocalMap变量        ThreadLocalMap map = getMap(t);        //到ThreadLocalMap中查找数据并返回        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")                T result = (T)e.value;                return result;            }        
        return setInitialValue();    }  // 设置ThreadLocal的value值  public void set(T value) {        //获取当前线程        Thread t = Thread.currentThread();        //又是跟上面一样,拿取当前线程的ThreadLocalMap变量        ThreadLocalMap map = getMap(t);        //存储数据        if (map != null)            map.set(this, value);        else            createMap(t, value);    }}

从ThreadLocal的源码可以看到,三个核心方法的思路其实都是如出一辙的。通过Thread.currentThread()获取当前线程,因为在最早分析Thread类时,我们就提到每个Thread都有一个自己的ThreadLocalMap成员变量,所以拿到当前线程便可以轻松访问到当前线程的ThreadLocalMap对象,那样存数据、取数据等操作直接由ThreadLocalMap完成就行了。因为每个线程拿到ThreadLocalMap对象不同,所以存储的数据做到了线程隔离,这也是ThreadLocal最为重要的地方。

3、上面源码理解了,ThreadLocal的原理也就没问题了,稍微简单明了的总结一下。

每个线程都有一个自己的ThreadLocalMap对象,ThreadLocalMap对象里维护了一个K-V格式的Entry数组,Key存的是ThreadLocal对象本身,value存储需要的值。当我们要操作ThreadLocal变量时,会先获取当前的线程,根据当前线程拿到对应的ThreadLocalMap对象,进而操作ThreadLocalMap内部数组里存储的数据。

 喜欢的请转发分享

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值