ThreadLocal(1):ThreadLocal介绍

文章介绍了Java中的ThreadLocal类,它用于在多线程环境中提供独立的局部变量,确保每个线程有自己的副本,避免了数据竞争。对比了ThreadLocal与synchronized在处理线程同步上的区别,推荐在需要线程隔离场景下使用ThreadLocal。

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

1 官方介绍

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 *
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // Atomic integer containing the next thread ID to be assigned
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // Thread local variable containing each thread's ID
 *     private static final ThreadLocal&lt;Integer&gt; threadId =
 *         new ThreadLocal&lt;Integer&gt;() {
 *             &#64;Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // Returns the current thread's unique ID, assigning it if necessary
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>Each thread holds an implicit reference to its copy of a thread-local
 * variable as long as the thread is alive and the {@code ThreadLocal}
 * instance is accessible; after a thread goes away, all of its copies of
 * thread-local instances are subject to garbage collection (unless other
 * references to these copies exist).
 *
 * @author  Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
    ...

从Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。

我们可以得知 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。

总结:

  1. 线程并发: 在多线程并发的场景下
  2. 传递数据: 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
  3. 线程隔离: 每个线程的变量都是独立的,不会互相影响

2 基本使用

2.1 常用方法

​ 在使用之前,我们先来认识几个ThreadLocal的常用方法

方法声明描述
ThreadLocal()创建ThreadLocal对象
public void set( T value)设置当前线程绑定的局部变量
public T get()获取当前线程绑定的局部变量
public void remove()移除当前线程绑定的局部变量

2.2 使用案例

我们来看下面这个案例 , 感受一下ThreadLocal 线程隔离的特点:

/*
*  需求: 线程隔离
*       在多线程并发的场景下, 每个线程中的变量都是相互独立
*       线程A :  设置(变量1 )   获取(变量1)
*       线程B :  设置(变量2 )   获取(变量2)
*
* */
public class MyDemo01 {

    //变量
    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        MyDemo01 demo = new MyDemo01();

        for (int i = 0; i < 20; i++) {

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    /*
                        每个线程: 存一个变量 , 过一会 取出这个变量
                     */
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("-----------------------");
                    System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                }
            });

            thread.setName("线程" + i); //线程0~4
            thread.start();
        }
    }

}

打印结果如下:

-----------------------
-----------------------
线程3--->线程3的数据
-----------------------
-----------------------
线程1--->线程3的数据
线程2--->线程3的数据
线程0--->线程3的数据
-----------------------
线程4--->线程4的数据
-----------------------
线程6--->线程6的数据
-----------------------
-----------------------
线程7--->线程7的数据
线程5--->线程7的数据
-----------------------
线程8--->线程8的数据
-----------------------
线程9--->线程9的数据
-----------------------
线程17--->线程17的数据
-----------------------
线程12--->线程12的数据
-----------------------
线程13--->线程13的数据
-----------------------
线程18--->线程18的数据
-----------------------
线程19--->线程19的数据
-----------------------
线程15--->线程19的数据
-----------------------
线程11--->线程11的数据
-----------------------
线程10--->线程10的数据
-----------------------
线程16--->线程16的数据
-----------------------
线程14--->线程14的数据

Process finished with exit code 0

从结果可以看出多个线程在访问同一个变量的时候出现的异常,线程间的数据没有隔离。下面我们来看下采用 ThreadLocal 的方式来解决这个问题的例子

/*
*  需求: 线程隔离
*       在多线程并发的场景下, 每个线程中的变量都是相互独立
*       线程A :  设置(变量1 )   获取(变量1)
*       线程B :  设置(变量2 )   获取(变量2)
*
*       ThreadLocal :
*           1. set() :  将变量绑定到当前线程中
*           2. get() :  获取当前线程绑定的变量
* */
public class MyDemo01 {

    ThreadLocal<String> tl = new ThreadLocal<>();

    //变量
    private String content;

    private String getContent() {
        return tl.get();
    }

    private void setContent(String content) {
        //变量content绑定到当前线程
        tl.set(content);
    }

    public static void main(String[] args) {
        MyDemo01 demo = new MyDemo01();

        for (int i = 0; i < 20; i++) {

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    /*
                        每个线程: 存一个变量 , 过一会 取出这个变量
                     */
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("-----------------------");
                    System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                }
            });

            thread.setName("线程" + i); //线程0~4
            thread.start();
        }
    }

}

打印结果如下:

-----------------------
-----------------------
线程2--->线程2的数据
线程0--->线程0的数据
-----------------------
线程1--->线程1的数据
-----------------------
-----------------------
线程3--->线程3的数据
线程4--->线程4的数据
-----------------------
-----------------------
-----------------------
线程14--->线程14的数据
线程5--->线程5的数据
-----------------------
线程8--->线程8的数据
-----------------------
线程10--->线程10的数据
-----------------------
线程11--->线程11的数据
-----------------------
-----------------------
-----------------------
线程9--->线程9的数据
-----------------------
线程16--->线程16的数据
线程6--->线程6的数据
-----------------------
线程17--->线程17的数据
线程7--->线程7的数据
-----------------------
-----------------------
线程13--->线程13的数据
-----------------------
线程12--->线程12的数据
线程15--->线程15的数据
线程19--->线程19的数据
-----------------------
线程18--->线程18的数据

Process finished with exit code 0

从结果来看,这样很好的解决了多线程之间数据隔离的问题,十分方便。

3 ThreadLocal类与synchronized关键字

3.1 synchronized同步方式

​ 这里可能有的朋友会觉得在上述例子中我们完全可以通过加锁来实现这个功能。我们首先来看一下用synchronized代码块实现的效果:

/*
*  需求: 线程隔离
*       在多线程并发的场景下, 每个线程中的变量都是相互独立
*       线程A :  设置(变量1 )   获取(变量1)
*       线程B :  设置(变量2 )   获取(变量2)
*
* */
public class MyDemo02 {

    //变量
    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        MyDemo02 demo = new MyDemo02();

        for (int i = 0; i < 20; i++) {

            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    /*
                        每个线程: 存一个变量 , 过一会 取出这个变量
                     */
                    synchronized (MyDemo02.class){
                        demo.setContent(Thread.currentThread().getName() + "的数据");
                        System.out.println("-----------------------");
                        System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
                    }
                }
            });

            thread.setName("线程" + i); //线程0~4
            thread.start();
        }
    }

}

打印结果:

-----------------------
线程0--->线程0的数据
-----------------------
线程4--->线程4的数据
-----------------------
线程7--->线程7的数据
-----------------------
线程3--->线程3的数据
-----------------------
线程2--->线程2的数据
-----------------------
线程11--->线程11的数据
-----------------------
线程1--->线程1的数据
-----------------------
线程8--->线程8的数据
-----------------------
线程9--->线程9的数据
-----------------------
线程10--->线程10的数据
-----------------------
线程6--->线程6的数据
-----------------------
线程5--->线程5的数据
-----------------------
线程18--->线程18的数据
-----------------------
线程17--->线程17的数据
-----------------------
线程13--->线程13的数据
-----------------------
线程14--->线程14的数据
-----------------------
线程16--->线程16的数据
-----------------------
线程15--->线程15的数据
-----------------------
线程12--->线程12的数据
-----------------------
线程19--->线程19的数据

Process finished with exit code 0

​ 从结果可以发现, 加锁确实可以解决这个问题,但是在这里我们强调的是线程数据隔离的问题,并不是多线程共享数据的问题, 在这个案例中使用synchronized关键字是不合适的。

3.2 ThreadLocal与synchronized的区别

​虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题, 不过两者处理问题的角度和思路不同。

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

总结:在刚刚的案例中,虽然使用ThreadLocal和synchronized都能解决问题,但是使用ThreadLocal更为合适,因为这样可以使程序拥有更高的并发性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不死鸟.亚历山大.狼崽子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值