ThreadLocal实现

1:  ThreadLocal实现原理 

    多线程同时访问一个共享变量时容易出现并发问题 , 特别多线程需要一个共享变量进行写入的时候 ,为了保证线程安全,  一般需要使用者在访问共享变量的时候进行适当的同步, 

    如下图:    

       

    同步的措施一般是加锁, 这就需要使用者对锁有一定的了解,

    那么有没有一个方式,在创建一个变量时候,每个线程对其进行访问的时候访问的是自己线程的变量呢, 其实,ThreadLocal就是做这个事情的, 虽然ThreadLocal的出现并不为了解决上面的问题而出现的

ThreadLocal 是在 JDK 包里面提供的,它提供了线程本地变量,也就是如果你创建了一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作的自己本地内存里面的变量,从而避免了线程安全问题,创建一个 ThreadLocal 变量后每个线程会拷贝一个变量到自己本地内存,如下图


1.1: ThreadLocal的简单使用

     本节来看下 ThreadLocal 如何使用,从而加深理解,本例子开启了两个线程,每个线程内部设置了本地变量的值,然后调用 print 函数打印当前本地变量的值,如果打印后调用了本地变量的 remove 方法则会删除本地内存中的该变量,代码如下:

public class ThreadLocalTest {    //(1)打印函数
    static void print(String str){        //1.1  打印当前线程本地内存中localVariable变量的值
        System.out.println(str + ":" +localVariable.get());        //1.2 清除当前线程本地内存中localVariable变量
        //localVariable.remove();
    }    //(2) 创建ThreadLocal变量
    static ThreadLocal<String> localVariable = new ThreadLocal<>();    public static void main(String[] args) {        //(3) 创建线程one
        Thread threadOne = new Thread(new  Runnable() {            public void run() {                //3.1 设置线程one中本地变量localVariable的值
                localVariable.set("threadOne local variable");                //3.2 调用打印函数
                print("threadOne");                //3.3打印本地变量值
                System.out.println("threadOne remove after" + ":" +localVariable.get());

            }
        });        //(4) 创建线程two
        Thread threadTwo = new Thread(new  Runnable() {            public void run() {                //4.1 设置线程one中本地变量localVariable的值
                localVariable.set("threadTwo local variable");                //4.2 调用打印函数
                print("threadTwo");                //4.3打印本地变量值
                System.out.println("threadTwo remove after" + ":" +localVariable.get());

            }
        });        //(5)启动线程
        threadOne.start();
        threadTwo.start();
    }
运行结果:

    

threadOne:threadOne local variable
threadTwo:threadTwo local variable
threadOne remove after:threadOne local variable
threadTwo remove after:threadTwo local variable
  • 代码(2)创建了一个 ThreadLocal 变量;

  • 代码(3)、(4)分别创建了线程 one 和 two;

  • 代码(5)启动了两个线程;

  • 线程 one 中代码 3.1 通过 set 方法设置了 localVariable 的值,这个设置的其实是线程 one 本地内存中的一个拷贝,这个拷贝线程 two 是访问不了的。然后代码 3.2 调用了 print 函数,代码 1.1 通过 get 函数获取了当前线程(线程 one)本地内存中 localVariable 的值;

  • 线程 two 执行类似线程 one。

解开代码 1.2 的注释后,再次运行,运行结果为:

    
threadOne:threadOne local variable
threadOne remove after:nullthreadTwo:threadTwo local variable
threadTwo remove after:null
1.2 ThreadLocal 实现原理

首先看下 ThreadLocal 相关的类的类图结构。




如上类图可知 Thread 类中有一个 threadLocals 和 inheritableThreadLocals 都是 ThreadLocalMap 类型的变量,而 ThreadLocalMap 是一个定制化的 Hashmap,默认每个线程中这个两个变量都为 null,只有当前线程第一次调用了 ThreadLocal 的 set 或者 get 方法时候才会进行创建。

其实每个线程的本地变量不是存放到 ThreadLocal 实例里面的,而是存放到调用线程的 threadLocals 变量里面。也就是说 ThreadLocal 类型的本地变量是存放到具体的线程内存空间的。

ThreadLocal 就是一个工具壳,它通过 set 方法把 value 值放入调用线程的 threadLocals 里面存放起来,当调用线程调用它的 get 方法时候再从当前线程的 threadLocals变 量里面拿出来使用。

如果调用线程一直不终止,那么这个本地变量会一直存放到调用线程的 threadLocals 变量里面,所以当不需要使用本地变量时候可以通过调用 ThreadLocal 变量的 remove 方法,从当前线程的 threadLocals 里面删除该本地变量。

另外 Thread 里面的 threadLocals 为何设计为 map 结构呢?很明显是因为每个线程里面可以关联多个 ThreadLocal 变量。

下面简单分析下 ThreadLocal 的 set,get,remove 方法的实现逻辑:

  • void set(T value)

    public void set(T value) {        //(1)获取当前线程
        Thread t = Thread.currentThread();        //(2)当前线程作为key,去查找对应的线程变量,找到则设置
        ThreadLocalMap map = getMap(t);        if (map != null)
            map.set(this, value);        else
        //(3)第一次调用则创建当前线程对应的HashMap
            createMap(t, value);
    }

如上代码(1)首先获取调用线程,然后使用当前线程作为参数调用了 getMap(t) 方法,getMap(Thread t) 代码如下:

 ThreadLocalMap getMap(Thread t) {        return t.threadLocals;
    }

可知 getMap(t) 所做的就是获取线程自己的变量 threadLocals,threadlocal 变量是绑定到了线程的成员变量里面。

如果 getMap(t) 返回不为空,则把 value 值设置进入到 threadLocals,也就是把当前变量值放入了当前线程的内存变量 threadLocals,threadLocals 是个 HashMap 结构,其中 key 就是当前 ThreadLocal 的实例对象引用,value 是通过 set 方法传递的值。

如果 getMap(t) 返回空那说明是第一次调用 set 方法,则创建当前线程的 threadLocals 变量,下面看 createMap(t, value) 里面做了啥呢?

     void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

可知就是创建当前线程的 threadLocals 变量。

  • T get()

    public T get() {        //(4) 获取当前线程
        Thread t = Thread.currentThread();        //(5)获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);        //(6)如果threadLocals不为null,则返回对应本地变量值
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null) {                @SuppressWarnings("unchecked")
                T result = (T)e.value;                return result;
            }
        }        //(7)threadLocals为空则初始化当前线程的threadLocals成员变量
                return setInitialValue();
    }

如上代码(4)首先获取当前线程实例,如果当前线程的 threadLocals 变量不为 null 则直接返回当前线程绑定的本地变量。否者执行代码(7)进行初始化,setInitialValue() 的代码如下:

    private T setInitialValue() {        //(8)初始化为null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);        //(9)如果当前线程的threadLocals变量不为空
        if (map != null)
            map.set(this, value);        else
        //(10)如果当前线程的threadLocals变量为空
            createMap(t, value);        return value;
    }
    protected T initialValue() {        return null;
    }

如上代码如果当前线程的 threadLocals 变量不为空,则设置当前线程的本地变量值为 null,否者调用 createMap 创建当前线程的 createMap 变量。

  • void remove()

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

如上代码,如果当前线程的 threadLocals 变量不为空,则删除当前线程中指定 ThreadLocal 实例的本地变量。

注:每个线程内部都有一个名字为 threadLocals 的成员变量,该变量类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 则为我们 set 时候的值,每个线程的本地变量是存到线程自己的内存变量 threadLocals 里面的,如果当前线程一直不消失那么这些本地变量会一直存到,所以可能会造成内存泄露,所以使用完毕后要记得调用 ThreadLocal 的 remove 方法删除对应线程的 threadLocals 中的本地变量。

1.3 子线程中获取不到父线程中设置的 ThreadLocal 变量的值

首先看个例子说明标题的意思:

public class TestThreadLocal {    //(1) 创建线程变量
    public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();    public static void main(String[] args) {        //(2)  设置线程变量
        threadLocal.set("hello world");        //(3) 启动子线程
        Thread thread = new Thread(new  Runnable() {            public void run() {                //(4)子线程输出线程变量的值
                System.out.println("thread:" + threadLocal.get());

            }
        });
        thread.start();        //(5)主线程输出线程变量值
        System.out.println("main:" + threadLocal.get());

    }
}

结果为:

main:hello world
thread:null

也就是说同一个 ThreadLocal 变量在父线程中设置值后,在子线程中是获取不到的。

根据上节的介绍,这个应该是正常现象,因为子线程调用 get 方法时候当前线程为子线程,而调用 set 方法设置线程变量是 main 线程,两者是不同的线程,自然子线程访问时候返回 null,那么有办法让子线程访问到父线程中的值吗?










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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值