详解Java中ThreadLocal类型及简单用法

作者:Mr.Ymx 时间:2022-03-09 11:51:51 

1 基本概念

ThreadLocal类提供了线程局部变量。这些变量与普通变量的不同之处在于,每个访问一个变量(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程关联起来的类中的私有静态字段(例如,用户ID或事务ID)。

例如,下面的类生成每个线程本地的唯一标识符。 线程的id在第一次调用ThreadId.get()时被赋值,并且在后续调用中保持不变。


public class ThreadId {
   // 包含要分配的下一个线程ID的原子整数
   private static final AtomicInteger nextId = new AtomicInteger(0);

// 包含每个线程ID的线程局部变量
   private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
       @Override
       protected Integer initialValue() {
           return nextId.getAndIncrement();
       }
   };

// 返回当前线程的唯一ID,并在必要时赋值
   public static int get() {
       return threadId.get();
   }
}

只要线程是活的并且ThreadLocal实例是可访问的,每个线程都持有一个对线程局部变量副本的隐式引用; 当一个线程离开后,它的所有线程本地实例副本都将被垃圾收集(除非存在对这些副本的其他引用)

2 简单使用


private static ThreadLocal<String> threadLocal = new ThreadLocal();

private static void print(String thread) {
   //打印当前线程中本地内存中本地变量的值
   System.out.println(thread + " :" + threadLocal.get());
   //清除本地内存中的本地变量
   threadLocal.remove();
}

public static void main(String[] args) {
   new Thread(() -> {
       //设置线程1副本变量的值
       threadLocal.set("I am Thread1");
       //调用打印方法
       print("thread1");
       //打印本地变量
       System.out.println("after remove : " + threadLocal.get());
   }).start();

new Thread(() -> {
       //设置线程2副本变量的值
       threadLocal.set("I am Thread2");
       //调用打印方法
       print("thread2");
       System.out.println("after remove : " + threadLocal.get());
   }).start();
}

运行结果:

thread1 :I am Thread1
thread2 :I am Thread2
after remove : null
after remove : null

由上边的程序可以看出,ThreadLocal就是将本地变量在多线程访问条件下给每个线程一个副本变量,图示:

详解Java中ThreadLocal类型及简单用法

3 应用场景

最典型应用场景就是Spring的声明式事务、 解决数据库连接、Session管理

那数据库链接为例:

将数据库的链接示例在每个线程中都有一份副本数据,在某一个线程关闭连接的时候就不会关闭其他链接,session同理。


private static final ThreadLocal<Connection> connection = new ThreadLocal() {
   @Override
   protected Object initialValue() {
       return Connection.getConnection();
   }
};

private static Connection getConnections() {
   return connection.get();
}

public static void main(String[] args) {
   new Thread(() -> {
       Connection connection = getConnections();
       //不会关闭其他链接
       connection.close();
   }).start();
   new Thread(() -> {
       Connection connection = getConnections();
       connection.close();
   }).start();
}

4 底层原理

ThreadLocal类型主要有3个方法和一个数据结构,分别是get()、set(Object)、remove()及ThreadLocalMap

4.1 set(Object)

将该线程局部变量的当前线程副本设置为指定的值。 大多数子类都不需要重写这个方法,只依赖于initialValue方法来设置线程局部变量的值。

参数: Value -要存储在当前线程本地线程的副本中的值。


public void set(T value) {
   //获取调用者线程
   Thread t = Thread.currentThread();
   //以当前线程作为key值,去查找对应的线程变量,找到对应的map
   ThreadLocalMap map = getMap(t);
   //如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
   if (map != null)
       map.set(this, value);
   //如果map为null,说明首次添加,需要首先创建出对应的map
   else
       createMap(t, value);
}

4.2 get()

返回该线程局部变量的当前线程副本中的值。 如果变量在当前线程中没有值,则首先将其初始化为initialValue方法调用所返回的值。

返回: 这个线程本地的当前线程值


public T get() {
   Thread t = Thread.currentThread();
   //获取当前线程的threadLocals变量
   ThreadLocalMap map = getMap(t);
   //如果threadLocals变量不为null,就可以在map中查找到本地变量的值
   if (map != null) {
       ThreadLocalMap.Entry e = map.getEntry(this);
       if (e != null) {
           @SuppressWarnings("unchecked")
           T result = (T)e.value;
           return result;
       }
   }
   //执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
   return setInitialValue();
}

private T setInitialValue() {
   //protected T initialValue() {return null;}
   T value = initialValue();
   //获取当前线程
   Thread t = Thread.currentThread();
   //以当前线程作为key值,去查找对应的线程变量,找到对应的map
   ThreadLocalMap map = getMap(t);
   //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
   if (map != null)
       map.set(this, value);
   //如果map为null,说明首次添加,需要首先创建出对应的map
   else
       createMap(t, value);
   return value;
}

4.3 remove()

移除此线程局部变量的当前线程值。如果这个线程局部变量随后被当前线程读取,它的值将通过调用它的initialValue方法重新初始化,除非它的值是由当前线程在中间设置的。这可能导致在当前线程中多次调用initialValue方法。


public void remove() {
   //获取当前线程绑定的threadLocals
   ThreadLocalMap m = getMap(Thread.currentThread());
   //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
   if (m != null)
       m.remove(this);
}

4.4 ThreadLocalMap


static class ThreadLocalMap {
   static class Entry extends WeakReference<ThreadLocal<?>> {
       Object value;
       Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
       }
   }
   private static final int INITIAL_CAPACITY = 16;
   //有效Entry数组
   private Entry[] table;
   //大小
   private int size = 0;
   //负载因子
   private int threshold; // Default to 0
}

5 内存泄漏隐患和防止策略

5.1 为什么会发生内存泄漏?

在线程池中线程的存活时间太长,往往都是和程序同生共死的,这样 Thread 持有的 ThreadLocalMap 一直都不会被回收,再加上 ThreadLocalMap 中的 Entry 对 ThreadLocal 是弱引用(WeakReference),所以只要 ThreadLocal 结束了自己的生命周期是可以被回收掉的。
Entry 中的 Value 是被 Entry 强引用的,即便 value 的生命周期结束了,value 也是无法被回收的,导致内存泄露。

5.2 怎样防止内存泄漏?

  • ThreadLocal申明为private static final xxx

  • ThreadLocal使用后务必调用remove方法。

参考文章:

https://www.cnblogs.com/fsmly/p/11020641.html

https://blog.csdn.net/meism5/article/details/90413860

https://blog.csdn.net/zzg1229059735/article/details/82715741

来源:https://blog.csdn.net/Mr_YanMingXin/article/details/120743280

标签:Java,ThreadLocal,类型
0
投稿

猜你喜欢

  • mybatis-plus使用问题小结

    2023-10-30 06:45:58
  • Java SpringBoot集成ChatGPT实现AI聊天

    2021-08-21 21:55:23
  • Java 实战练手项目之医院预约挂号系统的实现流程

    2023-11-24 00:42:36
  • Java排序之冒泡排序的实现与优化

    2023-11-10 21:35:56
  • 基于Java实现的图的广度优先遍历算法

    2021-06-02 06:51:20
  • java 字段值为null,不返回该字段的问题

    2023-07-13 10:32:34
  • Android使用JobScheduler定期推送本地通知实例代码

    2023-07-26 22:43:28
  • Java 中解决Unsupported major.minor version 51.0的问题

    2022-07-22 03:53:08
  • MyBatis-Plus分页插件不生效的解决方法

    2023-03-10 20:24:58
  • 四种引用类型在JAVA Springboot中的使用详解

    2023-11-24 03:34:38
  • 深入理解Java注解类型(@Annotation)

    2022-11-14 17:28:42
  • 如何设计一个安全的API接口详解

    2023-03-06 14:57:03
  • SpringBoot2之PUT请求接收不了参数的解决方案

    2023-08-23 01:32:07
  • Spring源码之循环依赖之三级缓存详解

    2021-11-13 09:31:56
  • SpringCloud微服务架构实战之微服务治理功能的实现

    2023-07-20 09:06:38
  • 手把手教你SpringBoot快速集成Swagger的配置过程

    2023-10-30 01:16:22
  • 浅析Java SPI 与 dubbo SPI

    2021-10-23 16:54:12
  • java获取网络图片上传到OSS的方法

    2023-10-14 23:01:07
  • Java这个名字的来历与优势

    2023-03-27 18:28:40
  • Flutter 分页功能表格控件详细解析

    2023-09-22 20:02:45
  • asp之家 软件编程 m.aspxhome.com