前言: Threadlocal线程本地变量的例子。
ThreadlocalUtils.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| 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(); } }
|
===>
1 2 3 4 5 6 7 8 9 10 11 12
| 当前线程是:thread2 threadlocal初始化,值为空 threadlocal初始化,值为空 当前线程是:thread1 测试共享变量 threadlocal初始化,值为空 当前线程是:thread0 不同一线程内不共享threadlocal 测试共享变量 当前线程是:thread0 同一线程内共享threadlocal 共享变量值为:测试共享变量
|
main线程与异步线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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
,就不存在安全问题了。在线程的整个使用过程中,只需要创建一次,又避免了频繁创建的开销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class ThreadlocalDateFormat {
static ThreadLocal<DateFormat> dateFormatThreadLocal = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); } };
public static String date2String(Date date) { return dateFormatThreadLocal.get().format(date); }
public static Date string2Date(String str) throws ParseException { return dateFormatThreadLocal.get().parse(str); } }
|
随机数
上下文信息
典型用途:提供上下文信息。
一个线程执行用户的请求,执行过程中,很多代码会访问一些共同的信息,例如用户身份信息、数据库连接、当前事务等,它们都是线程执行过程中的全局信息。
使用方法参数进行传递会很繁琐,使用Threadlocal会很方便,可以简化代码,避免传参,类似全局变量。
首次获取到信息,调用set()
方法进行设置,然后就可以在代码的任意地方调用get()
方法进行获取。
注意
- Threadlocal对象一般都定义为static,以便于引用。
- 如果在线程池里面设置了ThreadLocal变量,则一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,线程池里核心线程的threadlocals变量会一直持有ThreadLocal变量。
- 尽可能关掉线程(如果是在使用线程池的方案中,这恐怕很难做到)。
- 由于每个线程都有自己独立的变量,内存占用上会多一些,是一种以空间换时间的做法。通过增加资源来保证所有对象的线程安全。