Zacard's Notes

ThreadLocal的作用与原理

背景

spring默认bean的注册形式是单例模式。那spring是如何解决并发安全问题的呢?就是通过ThreadLocal。到底ThreadLocal有和“魔力”能让普通类变成线程安全的类呢?

原理

先来看看ThreadLocal.java的源码注释:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).

大致意思:

这个类提供线程局部变量。这种在多线程环境下访问(通过get或set方法)时,能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常是private static类型的,用于管理线程上下文。

也就是说,Threadlocal提供了作用范围为线程的局部变量,这种变量只在线程生命周期内起作用。减少了线程内多个方法之间公共变量传递的复杂度。

这里关于线程安全的类有一个普遍适用的原则:如果一个类没有实例私有属性,或者实例私有属性也是无状态的类,那么这个类就是无状态的类。而一个无状态的类肯定是线程安全的类。

而用ThreadLocal包装类的所有实例私有属性后,这个类就没有实例私有属性了,那么这个类就是一个无状态类,因此也是一个线程安全的类。这也是spring使用ThreeadLocal处理bean的默认方式。

ThreadLocal基本使用

先来看看ThreadLocal几个常用方法。

构造函数

1
2
3
4
5
6
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}

内部没有任何实现。

initialValue方法

1
2
3
protected T initialValue() {
return null;
}

initialValue()用来设置ThreadLocal的初始值。方法是protected的,建议在子类中被重载,以指定初始值。通常使用匿名内部类的形式。例如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* ThreadLocal测试类
*
* @author zacard
* @since 2016-05-23 16:36
*/
public class ThreadLocalTest {
private static final AtomicInteger nextId = new AtomicInteger(0);
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
}

withInitial

withInitial()也是用来初始化的,但是是lamda风格的初始化方法。构造方法中也是推荐使用此方法。例如以下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* ThreadLocal测试类
*
* @author zacard
* @since 2016-05-23 16:36
*/
public class ThreadLocalTest {
private static final AtomicInteger nextId = new AtomicInteger(0);
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(nextId::getAndIncrement);
}

使用测试代码

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
/**
* ThreadLocal测试类
*
* @author zacard
* @since 2016-05-23 16:36
*/
public class ThreadLocalTest {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(nextId::getAndIncrement);
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程{" + Thread.currentThread().getId() + "}的初始值为:" + threadLocal.get());
threadLocal.set(threadLocal.get() + 100);
System.out.println("线程{" + Thread.currentThread().getId() + "}的累加值为:" + threadLocal.get());
}
}).start();
}
}
}

线程{10}的初始值为:0
线程{12}的初始值为:2
线程{11}的初始值为:1
线程{11}的累加值为:101
线程{12}的累加值为:102
线程{10}的累加值为:100

由此可以看到,各个线程的threadlocal值是独立的。本线程对threadlocal中值的改动并没有影响到其他线程。

ThreadLocal的实现原理

先查看ThreadLocal的get()方法源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

其中getMap的源码:

1
2
3
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

setInitialValue的源码:

1
2
3
4
5
6
7
8
9
10
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}

createMap的源码:

1
2
3
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

查看以上源码后,我们可以了解get方法的流程:

1.首先获取当前线程
2.获取当前线程中的一个类型为ThreadLocalMap(这个类后面会讲到)的成员变量:threadLocals
3.如果threadLocalMap不为null,这通过当前ThreadLocal的引用作为key获取对应的value e。同时如果e不为null,返回e.value
4.如果threadLocalMap为null或者e为null,通过``setInitialValue``方法返回初始值。并且使用当前ThreadLocal的引用和value作为初始key与value创建一个新的threadLocalMap

总体设计思路:Thread维护了一个Map,key为ThreadLocal实例本身,value为真正需要存储的Object。

这样设计的好处:Map的Entry数量变小,性能提升。并且会随Thread一起销毁。

ThreadLocalMap解析

先查看源码:

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
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}

ThreadLocalMap是ThreadLocal的一个静态内部类。类上注释也解释了其基本实现方式:

ThreadLocalMap是一个自定义的hash map,只适合用来维护现场局部变量。并且是包级私有。hash表中的key是一个ThreadLocal的弱引用。当没有对ThreadLocal的强引用,并且发生GC时,该Entry必然会被回收。

这里的弱引用也保证了不会因为线程迟迟没有结束,而ThreadLocal的强引用不存在了,保存在ThreadLocalMap中的Entry却还依然存在。

坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章