详解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就是将本地变量在多线程访问条件下给每个线程一个副本变量,图示:
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 8 Instant 时间戳用于“时间戳”的运算。它是以Unix元年(传统 的设定为UTC时区1970年1月1日午夜时分)开始 所经
- Mybatis的日志模块的适配器模式我们在开发中日志是必不可少的一部分,而市场中有很多日志框架供我们使用,mybatis作为一个开源框架需要
- pageHelper分页失效及配置问题我在使用pageHelper的系统中加入mybatis-plus, 结果所有分页都失效了原因我这边的失
- 下面讲解以下在使用listview时最常见的几个问题。1.如何改变item的背景色和按下颜色listview默认情况下,item的背景色是黑
- 简单介绍synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:1. 修饰一个代码块,被修饰的代码块称为同步语句
- 废话不多说了,直接给大家贴java代码了。具体代码如下所示:/*支付流程*//****Controller.java 代码如下:*/@Req
- 一、题目描述题目:模拟一个简单的银行系统,使用两个不同的线程向同一个账户存钱。实现:使用特殊域变量volatile实现同步。二、解题思路创建
- import java.io.BufferedReader;import java.io.InputStreamReader;import
- 在这里,我们将用到finish(),简单介绍一下它的使用:finish()官方解析:Call this when your activity
- 本文实例为大家分享了Java实现置换密码加密解密,供大家参考,具体内容如下思路置换密码只不过是简单的换位而已,这里写的是一个分组长度为7的置
- 本文实例分析了C#中var关键字用法。分享给大家供大家参考。具体方法如下:C#关键字是伴随着.NET 3.5以后,伴随着匿名函数、LINQ而
- 在.Net中,对文件(File)和文件夹(Folder)的操作可以使用File类和Directory类,也可以使用FileInfo类和Dir
- 概要在使用IDEA开发微服务的时候,微服务比较多,启动起来比较麻烦,下面介绍一下使用批量启动微服务的方法。方法编辑当前项目根目录下的 .id
- 在同一个类中: 对于静态方法,其他的静态或非静态方法都可以直接调用它。而对于非静态方法,其他的非静态方法是可以直接调用它的。但是其他静态方法
- 之前一篇文章研究完横向二级菜单,发现其中使用了SparseArray去替换HashMap的使用.于是乎自己查了一些相关资料,自己同时对性能进
- Android 中Volley二次封装并实现网络请求缓存Android目前很多同学使用Volley请求网络数据,但是Volley没有对请求过
- Spring Cloud Gateway 服务网关API 主流网关有NGINX、ZUUL、Spring Cloud Gateway、Link
- 一. 假设需求场景在我们开发的过程中,经常出现两个对象存在一对多或多对一的关系。如何在程序在表明这两个对象的关系,以及如何利用这种关系优雅地
- CSV是一种通用的、相对简单的文件格式,最广泛的应用是在程序之间转移表格数据,而这些程序本身是在不兼容的格式上进行操作的。那么,C#如何读取
- 一、Synchronized的基本使用Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。Synchr