ThreadLocal使用与原理

在处理多线程并发安全的方法中,最常用的方法,就是使用锁,通过锁来控制多个不同线程对临界区的访问。但是,无论是什么样的锁,乐观锁或者悲观锁,都会在并发冲突的时候对性能产生一定的影响。那有没有一种方法,可以彻底避免竞争呢?
答案是肯定的,这就是ThreadLocal。
ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量

如何创建

1.直接new

private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();

2.创建并初始化

private static final ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
        
    /**
    * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
    */
    @Override
    protected String initialValue(){            
        System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
        return "这里是initialValue方法设置的默认值";        
    }    
};

使用

1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

private static final ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
    /**
     * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
     */
    @Override
    protected String initialValue(){
        System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
        return "这里是initialValue方法设置的默认值";
    }
};

public static void main(String[] args) {
    System.out.println("线程" + Thread.currentThread().getId() + ":" + TestCtrl.threadLocal.get());
    TestCtrl.threadLocal.set("1111");
    System.out.println("线程" + Thread.currentThread().getId() + ":" + TestCtrl.threadLocal.get());
    new Thread(new MyIntegerTask()).start();
}


public static class MyIntegerTask implements Runnable{

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getId() + ":" + TestCtrl.threadLocal.get());
        TestCtrl.threadLocal.set("222");
        System.out.println("线程" + Thread.currentThread().getId() + ":" + TestCtrl.threadLocal.get());
    }
    
}

执行结果
在这里插入图片描述
将 ThreadLocal 定义为 private static 是一种常见的做法,可以确保该 ThreadLocal 实例在整个应用程序或类的生命周期内是唯一的。这样做可以避免在多个地方创建多个 ThreadLocal 实例,确保线程间共享同一个 ThreadLocal 对象。

public class SharedThreadLocalExample {
    private static ThreadLocal<String> sharedThreadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            sharedThreadLocal.set("Value from Thread 1");
            System.out.println("Thread 1: " + sharedThreadLocal.get());
        });

        Thread thread2 = new Thread(() -> {
            sharedThreadLocal.set("Value from Thread 2");
            System.out.println("Thread 2: " + sharedThreadLocal.get());
        });

        thread1.start();
        thread2.start();
    }
}

在上述示例中,我们定义了一个 private static 的 ThreadLocal 实例 sharedThreadLocal。然后,我们创建了两个线程 thread1thread2,并在每个线程中使用共享的 sharedThreadLocal 实例。在每个线程中,我们使用 set() 方法设置线程局部变量的值,然后使用 get() 方法获取该值,并打印输出。
运行上述代码,您将看到两个线程分别设置并获取了相应的线程局部变量的值。由于 sharedThreadLocalprivate static,多个线程共享同一个实例,并且可以独立地设置和获取各自的线程局部变量的值。

请注意,尽管多个线程可以共享同一个 ThreadLocal 实例,但线程之间的线程局部变量是相互隔离的,每个线程对 ThreadLocal 的修改不会影响其他线程。这是 ThreadLocal 提供的线程隔离的特性。

ThreadLocal中的内存泄露

先简单看一下ThreadLocal的基本原理
在这里插入图片描述
通过观看ThreadLocal.get方法的源码我们可以看到,所谓的ThreadLocal变量就是保存在每个线程的map中的
ThreadLocalMap是一个比较特殊的Map,它的每个Entry的key都是一个弱引用。(当一个对象只被弱引用引用时,如果垃圾回收器发现该对象只有弱引用指向它,那么在下一次垃圾回收时,就会回收该对象)
这样设计的好处是,如果这个变量不再被其他对象使用时,可以自动回收这个ThreadLocal对象,避免可能的内存泄露
在这里插入图片描述
虽然ThreadLocalMap中的key是弱引用,当不存在外部强引用的时候,就会自动被回收,但是Entry中的value依然是强引用。即使线程退出,如果我们没有显示的调用remove方法,则启依然会存在内存中,而由于其是强引用,垃圾回收器又回收不了,则有可能发生内存泄露。
因此,我们可以通过以下方法避免ThreadLocal 内存泄露

  • 及时清理:在使用完 ThreadLocal 后,应该调用 remove() 方法清理其中的数据。可以在使用 ThreadLocal 的代码块结束时,或者在线程结束时执行清理操作。
  • 使用 try-finally:在某些情况下,当使用 ThreadLocal 存储的数据需要确保在代码块结束时被清理时,可以使用 try-finally 块来确保清理操作一定会执行。
  • 使用弱引用:在一些特定场景下,可以使用弱引用(WeakReference)来存储 ThreadLocal 变量的值,使其在不被使用时可以被垃圾回收。

总结起来,为了避免 ThreadLocal 导致的内存泄漏问题,需要及时清理 ThreadLocal 中的数据,并确保在适当的时机调用 remove() 方法。正确地管理 ThreadLocal 的生命周期可以避免不必要的内存占用和潜在的内存泄漏风险。

父子线程间可传递 InheritableThreadLocal

InheritableThreadLocal 是 ThreadLocal 的一个子类,它提供了一种在子线程中继承父线程中的线程局部变量的机制。当使用 InheritableThreadLocal 存储数据时,子线程可以访问并使用父线程中设置的值。
通常情况下,ThreadLocal 中存储的值只能在创建它的线程中访问和使用。但是,当使用 InheritableThreadLocal 时,子线程可以继承父线程中的 ThreadLocal 值,并在自己的执行过程中使用这些值。这对于一些需要在多个线程之间传递上下文信息的场景非常有用。
以下是对 InheritableThreadLocal 的一些要点进行解释:

  • 继承性:InheritableThreadLocal 允许子线程继承父线程的 ThreadLocal 值。在创建子线程时,子线程会自动拷贝父线程中的 ThreadLocal 值,使得子线程具有相同的 ThreadLocal 值副本。
  • 父子线程关系:InheritableThreadLocal 并不是线程之间共享数据,而是通过继承关系传递数据。父线程设置的值只能在其创建的子线程中访问,而子线程无法访问其他父线程或同级子线程的 ThreadLocal 值。
  • 用注意事项:尽管 InheritableThreadLocal 提供了线程间值的传递机制,但仍需要注意数据一致性和资源管理。当使用 InheritableThreadLocal 时,要确保正确设置和清理 ThreadLocal 值,避免数据泄漏和潜在的问题。
public class InheritableThreadLocalExample {
    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        inheritableThreadLocal.set("Parent Thread Value");

        Thread parentThread = new Thread(() -> {
            System.out.println("Parent Thread: " + inheritableThreadLocal.get());

            Thread childThread = new Thread(() -> {
                System.out.println("Child Thread: " + inheritableThreadLocal.get());
                inheritableThreadLocal.set("childThread 修改");
                Thread childThread1 = new Thread(() -> {
                    System.out.println("Child Thread1: " + inheritableThreadLocal.get());
                });
                childThread1.start();
            });

            childThread.start();
        });

        parentThread.start();
        Thread.sleep(2000L);
        System.out.println("main:" + inheritableThreadLocal.get());
    }
}

运行结果如下
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值