ThreadLocal的使用场景以及使用时要注意的问题

ThreadLocal的使用场景以及使用时要注意的问题

ThreadLocal的定义

ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,每个线程只能访问和修改自己的局部变量,变量彼此之间是相互隔离的,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。

ThreadLocal的使用

创建、设值、取值、删除

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("<----------"+count+"--------->");
System.out.println("第"+count+"次循环开始,当前线程中的局部变量值为:"+threadLocal.get());
threadLocal.remove();//当不做清除时候循环开始时获取的那个值是上次线程存储的,所以与多线程进行使用时注

使用场景

经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection
上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。

使用demo

@Test
    public void testThreadLocalTwo(){
        for (int i = 0; i < 3; i++) {//启动三个线程
            Thread t = new Thread() {
                @Override
                public void run() {
                    add10();
                }
            };
            t.start();
        }
    }

    static ThreadLocal<Integer> numThreadLocal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    /**
     * 线程本地存储变量加 5
     */
    private static void add10() {
        for (int i = 0; i < 5; i++) {
            Integer n = numThreadLocal.get();
            ++n;
            numThreadLocal.set(n);
            System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
        }
    }
Thread-2 : ThreadLocal num=1
Thread-1 : ThreadLocal num=1
Thread-0 : ThreadLocal num=1
Thread-1 : ThreadLocal num=2
Thread-2 : ThreadLocal num=2
Thread-1 : ThreadLocal num=3
Thread-0 : ThreadLocal num=2
Thread-1 : ThreadLocal num=4
Thread-2 : ThreadLocal num=3
Thread-1 : ThreadLocal num=5
Thread-0 : ThreadLocal num=3
Thread-2 : ThreadLocal num=4
Thread-0 : ThreadLocal num=4

最终结果可以看出来每个线程变量值的改变是互不影响的

ThreadLocal实现原理

首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。 因为一个线程内可以存在多个 ThreadLocal
对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是
ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set()
方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法。例如下面的 set 方法:

 /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    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 中使用的 key 为 ThreadLocal
的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。

所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap 中使用这个
ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的
value。

ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为
null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove()
方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。

为什么ThreadLocalMap的key是弱引用呢?
key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set, get,remove的时候会被清除。

解决方案
我们在使用完之后及时调用remove方法

ThreadLoca和多线程使用时注意的问题
demo

static  ExecutorService executorService = Executors.newSingleThreadExecutor();
    static  ThreadLocal<String> threadLocal = new ThreadLocal<>();
    @Test
    public void testThreadLocal(){
            for(int i=0;i<5;i++){
                 final int count = i;
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("第"+count+"次循环开始,当前线程中的局部变量值为:"+threadLocal.get());
                        threadLocal.set("<----------"+count+"--------->");
                        System.out.println("第"+count+"次循环结束,当前线程中的局部变量值为:"+threadLocal.get());
//                        threadLocal.remove();//当不做清除时候循环开始时获取的那个值是上次线程存储的,所以与多线程进行使用时注意要remove()掉
                        System.out.println("当前线程名字为:"+Thread.currentThread().getName());
                    }
                });
            }

    }

打印结果

0次循环开始,当前线程中的局部变量值为:null0次循环结束,当前线程中的局部变量值为:<----------0--------->
当前线程名字为:pool-1-thread-11次循环开始,当前线程中的局部变量值为:<----------0--------->1次循环结束,当前线程中的局部变量值为:<----------1--------->
当前线程名字为:pool-1-thread-12次循环开始,当前线程中的局部变量值为:<----------1--------->2次循环结束,当前线程中的局部变量值为:<----------2--------->
当前线程名字为:pool-1-thread-13次循环开始,当前线程中的局部变量值为:<----------2--------->3次循环结束,当前线程中的局部变量值为:<----------3--------->
当前线程名字为:pool-1-thread-14次循环开始,当前线程中的局部变量值为:<----------3--------->4次循环结束,当前线程中的局部变量值为:<----------4--------->
当前线程名字为:pool-1-thread-1

从打印结果可以看出后面线程在第一次获取的变量值得时候获取的是上一次线程中设置的变量值,原因就在于线程池的线程复用造成的,就是上一次的线程使用完变量没有及时做清理导致的,
解决方案也很简单 就是每次使用完之后及时清理, 调用threadLocal.remove();

0次循环开始,当前线程中的局部变量值为:null0次循环结束,当前线程中的局部变量值为:<----------0--------->
当前线程名字为:pool-1-thread-11次循环开始,当前线程中的局部变量值为:null1次循环结束,当前线程中的局部变量值为:<----------1--------->
当前线程名字为:pool-1-thread-12次循环开始,当前线程中的局部变量值为:null2次循环结束,当前线程中的局部变量值为:<----------2--------->
当前线程名字为:pool-1-thread-13次循环开始,当前线程中的局部变量值为:null3次循环结束,当前线程中的局部变量值为:<----------3--------->
当前线程名字为:pool-1-thread-14次循环开始,当前线程中的局部变量值为:null4次循环结束,当前线程中的局部变量值为:<----------4--------->
当前线程名字为:pool-1-thread-1

这是清除之后的打印结果,及时清理之后获取的值就是正常的

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal 是一种线程封闭技术,它提供了一种将数据与线程关联的方式。每个线程都有自己独立的 ThreadLocal 变量,该变量存储在当前线程的 ThreadLocalMap 中,可以在整个线程生命周期内被多个方法访问,但是对其他线程是不可见的。ThreadLocal 经常被用来实现线程安全的单例模式、数据库连接、Session 管理等。 ThreadLocal使用需要注意以下几点: 1. 内存泄漏问题:由于 ThreadLocalMap 的生命周期跟随线程的生命周期,如果使用不当,可能会导致内存泄漏。因此,在使用 ThreadLocal 时需要注意及时清理 ThreadLocal 变量。 2. 线程安全问题:虽然 ThreadLocal 变量在每个线程中都是独立的,但是在多线程环境中仍然需要注意线程安全问题。例如,如果多个线程同时访问同一个共享变量,并且将该变量存储在 ThreadLocal 中,那么仍然可能出现并发问题,需要使用线程同步机制来保证线程安全。 3. 对象状态问题:虽然 ThreadLocal 变量可以在多个方法中共享,但是在不同的方法中访问同一个 ThreadLocal 变量时,需要注意对象状态的一致性。例如,如果一个方法将某个对象存储在 ThreadLocal 中,另一个方法从 ThreadLocal 中获取该对象并修改其状态,那么这种修改可能会影响到其他方法,需要注意对象状态的一致性。 4. 性能问题:虽然 ThreadLocal 变量可以提高代码的可读性和可维护性,但是在性能要求较高的场景下,由于 ThreadLocal 的实现机制需要维护一个 ThreadLocalMap,可能会对性能产生一定的影响,需要谨慎使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值