ThreadLocal处理线程私有数据

本文详细介绍了Java中的ThreadLocal类,用于为每个线程创建独立的变量副本,避免线程安全问题。通过示例展示了ThreadLocal的使用,并分析了其原理,每个线程的ThreadLocal变量实际上是存储在ThreadLocalMap中。最后讨论了ThreadLocal可能导致的内存泄漏问题,建议在不再使用ThreadLocal时调用remove()方法进行清理。
摘要由CSDN通过智能技术生成

1. ThreadLocal简介

通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK中提供的 ThreadLocal 类正是为了解决这样的问题。

ThreadLocal 类主要解决的就是让每个线程绑定自己的值,可以将 ThreadLocal 类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据

如果你创建了一个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是 ThreadLocal 变量名的由来。他们可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题

2. ThreadLocal示例

import java.text.SimpleDateFormat;

import java.util.Random;

public class ThreadLocalExample implements Runnable{

//SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本

private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));

public static void main(String[] args) throws InterruptedException { ThreadLocalExample obj = new ThreadLocalExample();
for(int i=0 ; i<10; i++){

Thread t = new Thread(obj, ""+i);

Thread.sleep(new Random().nextInt(1000)); t.start();

}

}

@Override

public void run() {

System.out.println("Thread Name=

"+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());

try {

Thread.sleep(new Random().nextInt(1000));

} catch (InterruptedException e) { e.printStackTrace();

}

//formatter pattern is changed here by thread, but it won't reflect to other threads

formatter.set(new SimpleDateFormat());

System.out.println("Thread Name=

"+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
}
}

Output

Thread Name= 0 default Formatter = yyyyMMdd HHmm

Thread Name= 0 formatter = yy-M-d ah:mm

Thread Name= 1 default Formatter = yyyyMMdd HHmm

Thread Name= 2 default Formatter = yyyyMMdd HHmm

Thread Name= 1 formatter = yy-M-d ah:mm

Thread Name= 3 default Formatter = yyyyMMdd HHmm

Thread Name= 2 formatter = yy-M-d ah:mm

Thread Name= 4 default Formatter = yyyyMMdd HHmm

Thread Name= 3 formatter = yy-M-d ah:mm

Thread Name= 4 formatter = yy-M-d ah:mm

Thread Name= 5 default Formatter = yyyyMMdd HHmm

Thread Name= 5 formatter = yy-M-d ah:mm

Thread Name= 6 default Formatter = yyyyMMdd HHmm

Thread Name= 6 formatter = yy-M-d ah:mm

Thread Name= 7 default Formatter = yyyyMMdd HHmm

Thread Name= 7 formatter = yy-M-d ah:mm

Thread Name= 8 default Formatter = yyyyMMdd HHmm

Thread Name= 9 default Formatter = yyyyMMdd HHmm

Thread Name= 8 formatter = yy-M-d ah:mm

Thread Name= 9 formatter = yy-M-d ah:mm

从输出中可以看出,Thread-0已经改变了formatter的值,但仍然是thread-2默认格式化程序与初始化值相同,其他线程也一样。

上面有一段代码用到了创建 ThreadLocal 变量的那段代码用到了 Java8 的知识,它等于下面这段代码,如果你写了下面这段代码的话,IDEA会提示你转换为Java8的格式。因为ThreadLocal类在Java 8中扩展,使用一个新的方法 withInitial() ,将Supplier功能接口作为参数。

private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override

protected SimpleDateFormat initialValue()

{

return new SimpleDateFormat("yyyyMMdd HHmm");

}

};

3. ThreadLocal原理

public class Thread implements Runnable {

......

//与此线程有关的ThreadLocal值。由ThreadLocal类维护 
ThreadLocal.ThreadLocalMap threadLocals = null;

//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护 
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

......

}

从上面 Thread 类 源代码可以看出 Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把 ThreadLocalMap 理解为 ThreadLocal 类实现的定制化的 HashMap 。默认情况下这两个变量都是null,只有当前线程调用 ThreadLocal 类的 set 或 get 方法时才创建它们,实际上调用这两个方法的时候,我们调用的是 ThreadLocalMap 类对应的 get() 、 set() 方法。

ThreadLocal 类的 set() 方法

public void set(T value) {

Thread t = Thread.currentThread();

ThreadLocalMap map = getMap(t);

if (map êX null)

map.set(this, value);

else

createMap(t, value);

}

ThreadLocalMap getMap(Thread t) {

return t.threadLocals;

}

通过上面这些内容,我们足以通过猜测得出结论:最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上, ThreadLocal 可以理解为只是 ThreadLocalMap 的封装,传递了变量值。 ThrealLocal 类中可以通过 Thread.currentThread() 获取到当前线程对象后,直接通过 getMap(Thread t) 可以访问到该线程的 ThreadLocalMap 对象。

ThreadLocal 内部维护的是一个类似 Map 的 ThreadLocalMap 数据结构, key 为当前对象的 Thread 对象,值为 Object 对象。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

......

}

比如我们在同一个线程中声明了两个 ThreadLocal 对象的话,会使用 Thread 内部都是使用仅有那个 ThreadLocalMap 存放数据的, ThreadLocalMap 的 key 就是 ThreadLocal 对象,value就是 ThreadLocal 对象调用 set 方法设置的值。
在这里插入图片描述
ThreadLocalMap 是 ThreadLocal 的静态内部类。
在这里插入图片描述

4. ThreadLocal 内存泄露问题

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用Java的四种引用类型通俗易懂)。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。这样一来, ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施的话, value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 set() 、 get() 、 remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal 方法后 最好手动调用 remove() 方法

static class Entry extends WeakReference<ThreadLocal<?jk { /** The value associated with this ThreadLocal. */ Object value;

Entry(ThreadLocal<?> k, Object v) {

super(k);

value = v;

}

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值