searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

ThreadLocal

2024-03-28 09:29:34
2
0

一、ThreadLocal

ThreadLocal是本地线程变量,其中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。

它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本,通过统一的操作方式,实现了不同线程使用不通的变量,使用起来比较方便

Thread_1 --------> var_1
Thread_2 --------> var_2
Thread_3 --------> var_3
...

二、ThreadLocal怎么用

public class Main {
    public static void main(String[] args) {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        IntStream.range(0, 10).forEach(i -> {
            new Thread(() -> {
                threadLocal.set(i);
                // 业务逻辑...
                System.out.println(Thread.currentThread().getName() + "threadLocal var: " + threadLocal.get());
            }).start();
        });
    }
}

/**
 * Thread-0threadLocal var: 0
 * Thread-4threadLocal var: 4
 * Thread-3threadLocal var: 3
 * Thread-2threadLocal var: 2
 * Thread-1threadLocal var: 1
 */

可以看到在不同的线程中通过访问同一变量threadLocal,而获取到的是不同的值。

在一个应用系统中,一般是通过创建一个线程去负责处理业务逻辑,而业务逻辑的处理过程,通常需要跨越多个方法,多个方法之间可能需要相同的参数,我们称为context上下文信息,如果方法之间来回传递这个context是比较麻烦的,而ThreadLocal很好地解决了这个问题。

从实际应用的角度,ThreadLocal可以:

  1. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  2. 线程间数据隔离
  3. 进行事务操作,用于存储线程事务信息。
  4. 数据库连接,Session会话管理。

三、ThreadLocal的实现原理

Thread类中有两个变量:

  1. threadLocals
  2. inheritableThreadLocals

二者的类型都是ThreadLocal的内部类ThreadLocalMap类型,其类似HashMap,默认情况下二者都是null。当线程第一次调用ThreadLocal的get或set方法时会创建它们。

/---------------------------\			/-----------------------\
|				  			|			|						|
|		 	Thread	  		|			|	ThreadLocalMap		|
|							|			|						|
|1. threadLocals            |			|	Entry				|
|2. inheritableThreadLocals |			|		key | value		|
|							|			|		tl  |  var		|
|							|			|						|
\---------------------------/			\-----------------------/

ThreadLocal的set()方法:

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);
}

ThreadLocalMap getMap(Thread t) {
    // 获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
    return t.threadLocals; 
}

void createMap(Thread t, T firstValue) {
    // createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

get()方法:

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;
}

所以在不同的线程中通过get()方法获取threadLocal变量,本质上是从当前thread的threaLocals存的变量。

四、避免内存泄漏

在第三节介绍了每个Thread都维护了一个ThreadLocalMap,key为ThreadLocal实例,为弱引用,value为线程变量的副本,他们的引用关系:

										-------------------------------------Heap-----------------------\
/-----------Stack-----------\			|				------------\								 	|
|				  			|			|				|			|									|
|							|			|				v			|									|
|	ThreadLocalRef	--------------------|-----> ThreadLocal  		|		/-------------------\		|
|							|			|							|		|	ThreadLocalMap	|		|
|	CurrentTreadRef	--------------------|----->	CurrentThread ------|----->	|	Entry			|		|
|							|			|							|		|		key | value	|		|
|							|			|							|		|		tl  |  var  |		|
\---------------------------/			|							|		|		 |		|	|		|
										|							|		\--------|------|---/		|
										|							\----------------/		|			|
										\---------------------------------------------------------------/
																							|	
																					   		v
																					    线程变量val

从它们的引用关系可以看出来,threadLocalMap中的key为ThreadLocal的弱引用,当ThreadLocal在其他地方不存在强引用时,key(ThreadLocal)势必会被GC回收,这样key变成了null。而value还存在着强引用不会被GC回收,只有当当前进程结束这个强引用才会断开,但如果线程迟迟不结束,这个强引用就会一直存在,value就会一直存在内存中,造成内存泄漏。

因此如果threadlocal不再使用时,通过remove()方法删除,避免内存泄漏的情况发生。

0条评论
0 / 1000
cactusii
15文章数
0粉丝数
cactusii
15 文章 | 0 粉丝
原创

ThreadLocal

2024-03-28 09:29:34
2
0

一、ThreadLocal

ThreadLocal是本地线程变量,其中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。

它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本,通过统一的操作方式,实现了不同线程使用不通的变量,使用起来比较方便

Thread_1 --------> var_1
Thread_2 --------> var_2
Thread_3 --------> var_3
...

二、ThreadLocal怎么用

public class Main {
    public static void main(String[] args) {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        IntStream.range(0, 10).forEach(i -> {
            new Thread(() -> {
                threadLocal.set(i);
                // 业务逻辑...
                System.out.println(Thread.currentThread().getName() + "threadLocal var: " + threadLocal.get());
            }).start();
        });
    }
}

/**
 * Thread-0threadLocal var: 0
 * Thread-4threadLocal var: 4
 * Thread-3threadLocal var: 3
 * Thread-2threadLocal var: 2
 * Thread-1threadLocal var: 1
 */

可以看到在不同的线程中通过访问同一变量threadLocal,而获取到的是不同的值。

在一个应用系统中,一般是通过创建一个线程去负责处理业务逻辑,而业务逻辑的处理过程,通常需要跨越多个方法,多个方法之间可能需要相同的参数,我们称为context上下文信息,如果方法之间来回传递这个context是比较麻烦的,而ThreadLocal很好地解决了这个问题。

从实际应用的角度,ThreadLocal可以:

  1. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  2. 线程间数据隔离
  3. 进行事务操作,用于存储线程事务信息。
  4. 数据库连接,Session会话管理。

三、ThreadLocal的实现原理

Thread类中有两个变量:

  1. threadLocals
  2. inheritableThreadLocals

二者的类型都是ThreadLocal的内部类ThreadLocalMap类型,其类似HashMap,默认情况下二者都是null。当线程第一次调用ThreadLocal的get或set方法时会创建它们。

/---------------------------\			/-----------------------\
|				  			|			|						|
|		 	Thread	  		|			|	ThreadLocalMap		|
|							|			|						|
|1. threadLocals            |			|	Entry				|
|2. inheritableThreadLocals |			|		key | value		|
|							|			|		tl  |  var		|
|							|			|						|
\---------------------------/			\-----------------------/

ThreadLocal的set()方法:

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);
}

ThreadLocalMap getMap(Thread t) {
    // 获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
    return t.threadLocals; 
}

void createMap(Thread t, T firstValue) {
    // createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

get()方法:

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;
}

所以在不同的线程中通过get()方法获取threadLocal变量,本质上是从当前thread的threaLocals存的变量。

四、避免内存泄漏

在第三节介绍了每个Thread都维护了一个ThreadLocalMap,key为ThreadLocal实例,为弱引用,value为线程变量的副本,他们的引用关系:

										-------------------------------------Heap-----------------------\
/-----------Stack-----------\			|				------------\								 	|
|				  			|			|				|			|									|
|							|			|				v			|									|
|	ThreadLocalRef	--------------------|-----> ThreadLocal  		|		/-------------------\		|
|							|			|							|		|	ThreadLocalMap	|		|
|	CurrentTreadRef	--------------------|----->	CurrentThread ------|----->	|	Entry			|		|
|							|			|							|		|		key | value	|		|
|							|			|							|		|		tl  |  var  |		|
\---------------------------/			|							|		|		 |		|	|		|
										|							|		\--------|------|---/		|
										|							\----------------/		|			|
										\---------------------------------------------------------------/
																							|	
																					   		v
																					    线程变量val

从它们的引用关系可以看出来,threadLocalMap中的key为ThreadLocal的弱引用,当ThreadLocal在其他地方不存在强引用时,key(ThreadLocal)势必会被GC回收,这样key变成了null。而value还存在着强引用不会被GC回收,只有当当前进程结束这个强引用才会断开,但如果线程迟迟不结束,这个强引用就会一直存在,value就会一直存在内存中,造成内存泄漏。

因此如果threadlocal不再使用时,通过remove()方法删除,避免内存泄漏的情况发生。

文章来自个人专栏
灾备平台
15 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0