详解Java中ThreadLocal类型及简单用法
目录
- 1 基本概念
- 2 简单使用
- 3 应用场景
- 4 底层原理
- 4.1 set(Object)
- 4.2 get()
- 4.3 remove()
- 4.4 ThreadLocalMap
- 5 内存泄漏隐患和防止策略
- 5.1 为什么会发生内存泄漏?
- 5.2 怎样防止内存泄漏?
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就是将本地变量在多线程访问条件下给每个线程一个副本变量,图示:
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
到此这篇关于详解Java中ThreadLocal类型及简单用法的文章就介绍到这了,更多相关Java中ThreadLocal类型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!