Threadlocal

前言: 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()方法进行获取。

注意

  1. Threadlocal对象一般都定义为static,以便于引用。
  2. 如果在线程池里面设置了ThreadLocal变量,则一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,线程池里核心线程的threadlocals变量会一直持有ThreadLocal变量。
  3. 尽可能关掉线程(如果是在使用线程池的方案中,这恐怕很难做到)。
  4. 由于每个线程都有自己独立的变量,内存占用上会多一些,是一种以空间换时间的做法。通过增加资源来保证所有对象的线程安全。