ThreadLocal的使用及其原理

1.ThreadLocal是个啥?

ThreadLocal–顾名思义,翻译成中文可以理解为:线程局部/本地变量。
这个玩意有什么用处,或者说为什么要有这么一个东西?在并发编程的时候,共享的成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步,示意如下:
在这里插入图片描述
同步的措施一般是加锁,这就需要使用者对锁有一定的了解, 这显然加重了使用者的负担,那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?示意如下:
在这里插入图片描述
ThreadLocal就可以做到这件事情(虽然ThreadLocal并不是为了解决这个问题而出现的)。ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面变量,从而避免了线程安全问题。

2.使用演示

下面将用一段代码,展示下ThreadLocal的简单使用


public class ThreadLocalSimpleDemo {

    // 创建ThreadLocal变量-全局单例
    static ThreadLocal<String> localVariable = new ThreadLocal<>();

    public static void main(String[] args) {

        // 创建线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

                //设置线程中本地变量localVariable的值
                localVariable.set("thread local variable");

                //打印当前线程在本地内存中localVariable变量的值
                System.out.println("thread local variable is" + " : " + localVariable.get());

                //清除当前线程本地内存中的localVariable变量
                localVariable.remove();

                //清除后 打印当前线程在本地内存中localVariable变量的值
                System.out.println("after removing, thread local variable is" + " : " + localVariable.get());
            }
        });

        //启动线程
        thread.start();

    }
}

输出如下:
在这里插入图片描述

3.源码解读

可以先看下ThreadLocal的类图:
在这里插入图片描述
方法简介:
1.set():将变量设置到当前线程中
2.get(): 获取当前线程中的变量
3.remove():从当前线程中移除变量
3.setInitialValue(): 创建ThreadLocal时设置默认值
属性简介:
1.threadLocals:保存当前线程的变量数据,是一个map(其Entry是一个弱引用类型,后续说明)
2.inheritableThreadLocals: 保存线程的变量数据,当使用InheritableThreadLocal时,主线程的该字段不为空时,子线程的该字段也会被赋值(后续说明)

3.1 set()方法解读

在这里插入图片描述

3.2 get()方法解读

在这里插入图片描述

3.3 remove()方法解读

在这里插入图片描述

3.4 ThreadLocal的数据独立性

前文中有说到,ThreadLocal会为每个线程创建一份变量副本,如何体现?下面将用一个demo来演示下:

/**
 * 线程One的代码3.1通过set方法设置了localVariable的值,这其实设置的是线程
 * One本地内存中的个副本,这个副本线程Two是访问不了的 然后代码3.2调用了print
 * 函数,代码2.1通过get函数获取了当前线程(线程One本地内存中localVariable的值
 * 线程Two的执行类似于线程One。
 **/
public class ThreadLocalTest {

    //(1) 创建ThreadLocal变量-全局
    static ThreadLocal<String> localVariable = new ThreadLocal<>();

    //(2)print函数
    static void print(String str) {
        //2.1 打印当前线程在本地内存中localVariable变量的值
        System.out.println(str + ":" + localVariable.get());
        //2.2 清除当前线程本地内存中的localVariable变量
//        localVariable.remove();
    }


    public static void main(String[] args) {

        //(3) 创建线程One
        Thread threadOne = new Thread(new Runnable() {
            @Override
            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() {
            @Override
            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) 启动线程

        //5.1启动主线程
        localVariable.set("mainThread local variable");
        print("mainThread");
        System.out.println("mainThread remove after" + " : " + localVariable.get());
        
        //5.2启动子线程
        threadOne.start();
        threadTwo.start();

    }
}

输出如下:
在这里插入图片描述
解除2.2的注释,输出如下:
在这里插入图片描述

3.5 拓展:如何在子线程中访问主线程的变量(InheritableThreadLocal)

由上面的内容可以知道:各个线程在访问ThreadLocal中变量时是互不干扰的。但如果子线程想访问主线程的变量时,应该怎么做呢?
答:可以使用ThreadLocal的子类–InheritableThreadLocal,具体使用如下:


/**
 * 如何实现子线程访问主线程中的本地变量呢?如下
 **/
public class InheritableThreadLocalTest {

    //(1) 创建ThreadLocal变量-全局

    //1.1 使用原始的ThreadLocal--子线程不能访问主线程变量
//    static ThreadLocal<String> localVariable = new ThreadLocal<>();

    //1.2 使用子类InheritableThreadLocal--子线程可以访问主线程变量
    static InheritableThreadLocal<String> localVariable = new InheritableThreadLocal<>();


    //(2)print函数
    static void print(String str) {
        //2.1 打印当前线程在本地内存中localVariable变量的值
        System.out.println(str + ":" + localVariable.get());
    }


    public static void main(String[] args) {


        //(3) 主线程设置本地变量localVariable的值
        localVariable.set("mainThread local variable");
        //3.1 打印本地变量值
        print("mainThread");

        //(4) 创建线程One
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                //4.1 打印本地变量值
                print("threadOne");
            }
        });

        //(5) 创建线程Two
        Thread threadTwo = new Thread(new Runnable() {
            @Override
            public void run() {
                //5.1 打印本地变量值
                print("threadTwo");
            }
        });


        //(6) 启动线程
        threadOne.start();
        threadTwo.start();

    }
}

输出如下:
在这里插入图片描述
在这里插入图片描述

3.6 拓展:弱引用(WeakReference)的在GC中的逻辑

Java中的引用类型分为:强引用、软引用、弱引用和虚引用;GC的发生的可能性:虚引用>弱引用>软引用>强引用,具体如下:

  • 强引用:无论内存是否足够,不会回收。
  • 软引用:内存不足时,回收该引用关联的对象。
  • 弱引用:垃圾回收时,无论内存是否足够,都会回收。
  • 虚引用:任何时候都可能被垃圾回收器回收。

ThreadLocal.ThreadLocalMap的Entry是一个弱引用类型,弱引用的使用是为了避免内存泄漏
Java的GC分为Minor GC和Full GC:

  • 发生Minor GC时,如果是标记为WeakReference的对象,位置在新生代,而且只有弱引用,没有其他引用,则会被回收。
  • 发生Full GC时,如果是标记为WeakReference的对象,只有弱引用,无论在新生代还是老年代,都会被回收。

这说明,只要堆上的对象仅仅只被弱引用所指向,不管当前内存空间是否足够,下次GC都会回收对象的内存空间

3.7 拓展:Synchronized和ThreadLocal的对比

SynchronizedThreadLocal
原理同步机制采用了时间换空间的方式,只提供一份变量,让不同线程排队访问(临界区排队)采用空间换时间的方式,为每一个线程都提供一份变量的副本,从而实现同时访问而互不相干扰
侧重点多个线程之间访问资源的同步多线程中让每个线程之间的数据相互隔离

4.总结

  • 为了避免并发问题,相对于加锁,threadLocal是较为轻量的解决方案
  • ThreadLocal会为每个线程创建一个变量副本,访问是互不影响的
  • 子线程访问主线程的变量,可以使用InheritableThreadLocal
  • ThreadLocal的常见使用场景:全局存储用户信息
  • 弱引用的对象在GC时,无论是Minor GC还是Full GC都会被回收
  • ThreadLocal放在线程池中的坑:多个任务复用同一个线程时会有问题

5.参考资料

《Java并发编程之美》
ThreadLocal详解
WeakReference在GC中的逻辑
Java的四种引用方式

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值