前言: Threadlocal线程本地变量的例子。
ThreadlocalUtils.java
import java.util.HashMap;
import java.util.Map;
public class ThreadlocalUtils {
public static ThreadLocal<Map> threadlocal = new ThreadLocal() {
@Override
protected Object initialValue() {
System.out.println("threadlocal初始化,值为空");
return null;
}
};
public static void setData(String key, Object value) {
Map map = threadlocal.get();
if (map == null) {
map = new HashMap();
map.put(key, value);
threadlocal.set(map);
}
map.put(key, value);
}
public static Object getData(String key) {
Map map = threadlocal.get();
if (map != null) {
return map.get(key);
}
return null;
}
public static void clear() {
threadlocal.remove();
}
}
ThreadlocalUtilsTest.java
import java.util.HashMap;
import java.util.Map;
public class ThreadlocalUtils {
public static ThreadLocal<Map> threadlocal = new ThreadLocal() {
@Override
protected Object initialValue() {
System.out.println("threadlocal初始化,值为空");
return null;
}
};
public static void setData(String key, Object value) {
Map map = threadlocal.get();
if (map == null) {
map = new HashMap();
map.put(key, value);
threadlocal.set(map);
}
map.put(key, value);
}
public static Object getData(String key) {
Map map = threadlocal.get();
if (map != null) {
return map.get(key);
}
return null;
}
public static void clear() {
threadlocal.remove();
}
}
===>
当前线程是:thread2
threadlocal初始化,值为空
threadlocal初始化,值为空
当前线程是:thread1
测试共享变量
threadlocal初始化,值为空
当前线程是:thread0
不同一线程内不共享threadlocal
测试共享变量
当前线程是:thread0
同一线程内共享threadlocal
共享变量值为:测试共享变量
main线程与异步线程
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocalUtli.setTrace("外部变量");
System.out.println("current thread = " + Thread.currentThread().getName());
new Thread(
() -> {
System.out.println("current thread = " + Thread.currentThread().getName());
new Test1().test1();
}).start(); // 异步调用
new Test1().test1(); // 同步调用
new Test2().test2(); // 同步调用
}
}
可以进行初始化赋值,如果没有这一步的话,get()
方法获取到的就是null
。
现在的线程大多都为线程池管理,也就是存在线程复用的情况,线程处理完一个请求后,处理下一个请求也许是相同的线程。
如果上一个线程中的本地变量使用完毕后没有清理,下一个请求就会获取到上一个请求中的数据,这样就不安全。因此请求处理完毕之后,最好显式调用下remove()
方法清除变量中的数据。
防止下一个请求获取到。
tomcat的min-spare-threads
配置,最小工作线程的数量。
使用场景
日期处理
每个线程使用自己的DateFormat
,就不存在安全问题了。在线程的整个使用过程中,只需要创建一次,又避免了频繁创建的开销。
public class ThreadlocalDateFormat {
T static ThreadLocal<DateFormat> dateFormatThreadLocal = 'new ThreadLocal<DateFormat>() {+08 = 00'
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH =mm:ss");
}
};
T public static String date2String(Date date) {+08 = '00'
T return dateFormatThreadLocal.get().format(date);+08 = '00'
}
public static Date string2Date(String str) throws ParseException {
T return dateFormatThreadLocal.get().parse(str);+08 = '00'
}
}
随机数
上下文信息
典型用途:提供上下文信息。
一个线程执行用户的请求,执行过程中,很多代码会访问一些共同的信息,例如用户身份信息、数据库连接、当前事务等,它们都是线程执行过程中的全局信息。
使用方法参数进行传递会很繁琐,使用Threadlocal会很方便,可以简化代码,避免传参,类似全局变量。
首次获取到信息,调用set()
方法进行设置,然后就可以在代码的任意地方调用get()
方法进行获取。
注意
- Threadlocal对象一般都定义为static,以便于引用。
- 如果在线程池里面设置了ThreadLocal变量,则一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,线程池里核心线程的threadlocals变量会一直持有ThreadLocal变量。
- 尽可能关掉线程(如果是在使用线程池的方案中,这恐怕很难做到)。
- 由于每个线程都有自己独立的变量,内存占用上会多一些,是一种以空间换时间的做法。通过增加资源来保证所有对象的线程安全。