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

网友投稿 260 2022-12-01

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

目录1 基本概念2 简单使用3 应用场景4 底层原理4.1 set(Object)4.2 get()4.3 remove()4.4 ThreadLocalMap5 内存泄漏隐患和防止策略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 threadId = new ThreadLocal() {

@Override

protected Integer lHziQbSjHrinitialValue() {

return nextId.getAndIncrement();

}

};

// 返回当前线程的唯一ID,并在必要时赋值

public static int get() {

return threadId.get();

}

}

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

2 简单使用

private static ThreadLocal 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 = 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()

返回该线程lHziQbSjHr局部变量的当前线程副本中的值。 如果变量在当前线程中没有值,则首先将其初始化为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> {

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://cnblogs.com/fsmly/p/11020641.html

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

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

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SQL数据分析之流程控制语句【if,case...when详解】
下一篇:初学者python笔记(类的继承与多态---详解)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~