ThreadLocal的使用

对于ThreadLocal这个类既熟悉又陌生。熟悉是因为面试经常会被问到,所以背了很多概念;陌生是因为实际并没怎么用过(惭愧,我是菜鸡)。

这个周末不想出门,百无聊赖之中简单扒一扒它的用法。

ThreadLocal简介

ThreadLocal这个类在java的lang包下,和想象中不一样,它其实跟Thread本身没有直接关系,并没有继承Thread。

image-20230722174049473.png

比较关键的两个方法:get(),set()。

其中有个内部类:ThreadLocalMap也比较重要。

感兴趣的可以阅读下源码,加上注释只有720行,比较简单。

ThreadLocal的主要作用

  • 用于线程间的数据隔离,可以把它想象成一个存放变量的瓶子,每个线程过来访问的时候都会拿到一只,想要用变量的时候就把它从瓶子里拿出来,或者放一个新的进去,然后把盖子拧上,这样其他线程就拿它无可奈何啦。或者说得正式点:ThreadLocal为每个线程中的并发访问变量提供一个副本。注意要先放再取,不然会硌到手
  • 减少线程同步的资源消耗
  • Session会话处理
  • 存储线程事务信息

使用示例

创建一个类ContextUtil,这个类我们把它比喻成一个柜子,可以用来放很多ThreadLocal的瓶子。我们在里面放上两个瓶子,一个瓶子放User对象,一个瓶子放Integer对象。

public class ContextUtil {

    public static ThreadLocal<User> contextUser = new ThreadLocal<>();

    public static ThreadLocal<Integer> contextCount = new ThreadLocal<>();

}

User类

@Data
public class User implements Serializable {
    
    private Integer userId;

    private String userName;

}

现在测试一下:启动三个线程,每个线程都会从ContextUtil中拿到自己的两个ThreadLocal,并各自往里面放进对象,过几秒后再取出来确认一下有没有被其他线程偷偷换掉‘

public static void main(String[] args) {
    Thread t0 = new Thread(){
        @SneakyThrows
        @Override
        public void run() {
            ContextUtil.contextUser.set(new User(1, "1号"));
            ContextUtil.contextCount.set(1);

            //打开瓶子看一下
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());

            //强迫症犯了,再看一下
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
        }
    };
    t0.start();

    Thread t1 = new Thread(){
        @SneakyThrows
        @Override
        public void run() {
            ContextUtil.contextUser.set(new User(2, "2号"));
            ContextUtil.contextCount.set(2);

            //打开瓶子看一下
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());

            //强迫症犯了,再看一下
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
        }
    };
    t1.start();

    Thread t2 = new Thread(){
        @SneakyThrows
        @Override
        public void run() {
            ContextUtil.contextUser.set(new User(3, "3号"));
            ContextUtil.contextCount.set(3);

            //打开瓶子看一下
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());

            //强迫症犯了,再看一下
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextUser.get());
            System.out.println(Thread.currentThread().getName() + "-----" + ContextUtil.contextCount.get());
        }
    };
    t2.start();
}

结果

Thread-2-----User(userId=3, userName=3号)
Thread-2-----3
Thread-0-----User(userId=1, userName=1号)
Thread-1-----User(userId=2, userName=2号)
Thread-1-----2
Thread-0-----1

Thread-0-----User(userId=1, userName=1号)
Thread-0-----1
Thread-2-----User(userId=3, userName=3号)
Thread-2-----3
Thread-1-----User(userId=2, userName=2号)
Thread-1-----2

小结

从示例中可以看到:三个线程各自为ContextUtil中的两个ThreadLocal设置了不同的值,并且在整个运行期间都没有发生污染

ThreadLocal确实为每个线程创建了单独的变量副本

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容