详解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
投稿

猜你喜欢

  • 利用Kotlin + Spring Boot实现后端开发

    2022-02-20 05:30:19
  • 详解Spring Cloud Zuul 服务网关

    2021-11-15 19:24:19
  • Java URL自定义私有网络协议

    2021-08-11 02:21:06
  • C# DataSet查看返回结果集的实现

    2021-10-10 09:54:31
  • Android操作SQLite数据库(增、删、改、查、分页等)及ListView显示数据的方法详解

    2022-11-28 10:03:09
  • 简单了解Spring beanfactory循环依赖命名重复属性

    2023-10-27 19:39:14
  • Android开发之模仿微信打开网页的进度条效果(高仿)

    2021-09-02 10:17:04
  • JavaSwing FlowLayout 流式布局的实现

    2023-10-02 03:59:41
  • 如何使用ByteArrayOutputStream下载文件

    2021-07-29 11:32:45
  • SpringBoot和Swagger结合提高API开发效率

    2023-11-25 01:23:16
  • Android连接指定Wifi的方法实例代码

    2022-03-01 06:26:25
  • C#集合本质之队列的用法详解

    2023-03-17 06:42:38
  • Android中EditText屏蔽第三方输入法表情的方法示例

    2021-07-23 01:13:11
  • java基础的详细了解第八天

    2023-11-08 10:47:47
  • c#操作ftp类分享

    2023-02-27 12:50:17
  • java生成图片验证码实例代码

    2022-01-06 04:01:37
  • 深入解析C++编程中对设计模式中的策略模式的运用

    2022-05-26 22:52:20
  • Java分形绘制山脉模型

    2023-05-10 00:51:29
  • 通过Java连接SQL Server数据库的超详细操作流程

    2022-08-01 12:46:01
  • spring boot 使用profile来分区配置的操作

    2022-11-27 22:55:15
  • asp之家 软件编程 m.aspxhome.com