一、ThreadLocal简介
在多线程编程中通常解决线程安全问题的方式是使用synchronized对共享资源进行同步,但是这种加锁的方式会使未获得锁对象的线程进行阻塞等待。显然这种方式的时间效低。
线程安全问题的核心在于多个线程会对同一共享资源进行操作,那么,如果每个线程都使用自己的“共享资源”,彼此之间达到隔离的状态,就不会出现线程安全的问题。实际上,这是一种空间换时间的方案。每个线程都会都拥有自己的“共享资源”无疑内存消耗会很大,但是由于不需要同步也就减少了线程可能存在的阻塞等待的情况从而提高的时间效率。
虽然ThreadLocal并不在java.util.concurrent包中(java.lang),但是它更像一种并发容器。从ThreadLocal这个类名可以顾名思义,表示线程的“本地变量”,即每个线程都拥有该变量的副本,各用各的避免共享资源的竞争。
二、ThreadLocal实现原理
void set(T value) 设置当前线程的threadLocal变量的值
1
2
3
4
5
6
7
8
9
10
11
12public void set(T value) {
//获取当前线程的实例对象
Thread t = Thread.currentThread();
//通过当前线程实例获得ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
//如果map不为null,则以当前threadLocal实例为key,value为值存入
map.set(this, value);
else
//map为null,则新建ThreadLocalMap并存入value
createMap(t, value);
}
可以发现,值其实是存放在ThreadLocalMap中,那么通过getMap(t)可以查看ThreadLocalMap是怎样来的?1
2
3ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
所以ThreadLocalMap的引用是作为Thread的一个成员变量,被Thread类进行维护的。
1 | void createMap(Thread t, T firstValue) { |
该方法就是new一个ThreadLocalMap实例对象,然后同样以当前threadLocal实例作为key,值为value存放到threadLocalMap中,然后将当前线程对象的threadLocals赋值为threadLocalMap。
T get() 获取当前线程中threadLocal变量的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public T get() {
//获取当前线程的实例对象
Thread t = Thread.currentThread();
//获取当前线程的threadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取map中当前threadLocal实例为key的entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
"unchecked") (
//不为null,就返回相应的value
T result = (T)e.value;
return result;
}
}
//若map为null,或者entry为null,通过该方法初始化,并返回该方法返回的value
return setInitialValue();
}
1 | private T setInitialValue() { |
该初始化方法的逻辑与void set(T value)方法几乎一致。1
2
3protected T initialValue() {
return null;
}
该方法是protected修饰,也就是说继承ThreadLocal的子类可以重写该方法,实现赋值为其他初始值
void remove() 删除当前线程中以threadLocal实例为key的键值对
1
2
3
4
5
6
7public void remove() {
//获取当前线程的threadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//从map中删除以当前threadLocal实例为key的键值对
m.remove(this);
}
三、ThreadLocal的使用场景
ThreadLocal不是用来解决共享对象的多线程访问问题的,数据实质上是放在每个thread实例引用的threadLocalMap中,每个不同的线程都拥有自己专属的数据容器(threadLocalMap),彼此不影响。因此threadLocal只适用与共享数据会造成线程安全的场景。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
32public class ThreadLocalDemo {
private static ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<>();
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executorService.submit(new DateUtil("2019-11-25 09:00:" + i % 60));
}
}
static class DateUtil implements Runnable {
private String date;
public DateUtil(String date) {
this.date = date;
}
public void run() {
if (sdf.get() == null) {
sdf.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
} else {
try {
Date date = sdf.get().parse(this.date);
System.out.println(date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
}
}
如果当前线程不持有SimpleDateformat对象实例,那么就新建一个并把它设置到当前线程中,如果已经持有,就直接使用。另外,从 if (sdf.get() == null){….}else{…..}可以看出为每一个线程分配一个SimpleDateformat对象实例是从应用层面(业务代码逻辑)去保证的。