java 的 ThreadLocal

本文介绍了Java中的ThreadLocal,用于在多线程环境中提供线程安全的局部变量。ThreadLocal能够为每个线程创建独立的变量副本,避免了线程同步的问题。文章通过示例代码展示了ThreadLocal的使用,并探讨了ThreadLocal的上下文问题,包括InheritableThreadLocal的特性,即线程间的上下文继承。通过对ThreadLocal的了解,开发者可以更好地管理线程级别的数据,如数据库连接、HTTP请求和用户身份信息等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

除了前面一直讲的加锁的同步方式之外,还有一种保证一种规避多线程访问出现线程不安全的方法就是使用 ThreadLocal。

在多线程环境下,每个线程都有自己的数据,一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有自己的能看见,不会影响其他线程。

ThreadLocal 能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能够达到线程按安全的目的。说白了,ThreadLocal就是为了在多线程环境下保证成员变量的安全。

最常用的地方,就是为每个线程都绑定一个数据库连接,http请求、用户身份信息等,这样一个线程多所有调用到的方法就能很方便的访问这些资源;常用的方法有三个,get、set和initialValue。

jdk 建议将 threadLocal 定义为 private static。

一、ThreadLocal 示例

使用方法简单示例:

/**
* ThreadLocal:每个线程自己的数据,不会影响其他线程。
*/
public class MYThreadLocal {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);
    public static void main(String[] args) {
        for (int i=0; i<5; i++){
            new Thread(new Test()).start();
        }
    }

    public static class Test implements Runnable{
        @Override
        public void run() {
            Integer i = threadLocal.get();
            System.out.println(Thread.currentThread().getName()+"得到"+i);
            threadLocal.set(i-1);
            System.out.println(Thread.currentThread().getName()+"剩下"+threadLocal.get());
        }
    }
}

前面说了这个东西可以避免线程不安全,其实写完代码更能理解,就是直接规避了共同资源这一件事,本来就是在各用各的,没有公共资源的问题,所以也不牵扯线程安全了。也就是说的一个线程级别的变量

二、源码

ThreadLocal 的源码,如何实现这又是另外一个主题了:

(待更新链接)

三、ThreadLocal使用要注意的上下文问题

3.1 ThreadLocal

来看这个代码:

public class MYThreadLocal1 {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->1);
    public static void main(String[] args) {
        new Thread(new Test()).start();
    }

    public static class Test implements Runnable{
        public Test(){
            threadLocal.set(-100);
            System.out.println(Thread.currentThread().getName() + "是:"+threadLocal.get());
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"是:"+threadLocal.get());
        }
    }
}

他的输出是什么呢?

在这里插入图片描述
可以看到,输出的线程自己的独有的变量这一点,-100虽然是在 Test 里设置,可是修改了main线程:

  • 第一行,这个线程 Test 的构造器虽然 set 了值,但是,输出的结果对应的线程却是他所在的线程,也就是调用他的那个线程。
  • 而第二行,run 方法是由new 出的 Test 线程调用的,所以输出的结果对应的是他所在的线程。

也就是说,线程的构造器属于对应的线程体;而 run 方法则是属于本线程。

3.2 InheritableThreadLocal

顾名思义,继承线程。

使用这个类,可以把变量从上下文环境进行继承。

我们如果按照普通 ThreadLocal 写出:

public class MYThreadLocal1 {
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        threadLocal.set(2);
        System.out.println(Thread.currentThread().getName()+" :"+threadLocal.get());
        
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" :"+threadLocal.get());
        }).start();
    }
}

如果是这样的代码,会输出 2 和 null,因为线程各自拥有自己的变量,而第二个线程虽然在main线程内部,但是仍然是独立的。

如果使用InheritableThreadLocal,则会把上下文关系进行继承,修改类为:

private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();

则上面的程序会输出 2 和 2,相当于默认情况下,main线程会将自己的数据给子线程 拷贝 一份。

如果不想继承,当然子线程也可以自己改自己的。

private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
    threadLocal.set(2);
    System.out.println(Thread.currentThread().getName()+" :"+threadLocal.get());

    new Thread(()->{
        threadLocal.set(1);
        System.out.println(Thread.currentThread().getName()+" :"+threadLocal.get());
    }).start();
}

这样就会输出 2 和 1.1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值