关于 ThreadLocal,咱们常常用它来解决多线程并发问题,那它到底是如何作到的?今天就让咱们来好好看一下。 git
首先,让咱们看看 ThreadLocal 类中的介绍:github
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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).session
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).多线程
按照文中所述,ThreadLocal 提供的是线程本地变量,每一个线程都有一份单独的副本,常常使用的方式是私有静态变量
。关键在于下一段,线程存活,ThreadLocal 实例就能够被访问,线程消失,就会被垃圾回收。并发
看到这儿,有没有想起上一篇内容所说的引用类型
,有多是软引用
或者弱引用
,具体是什么呢?仍是来看看代码:app
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取线程里的map
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}复制代码
上面展现的是 ThreadLocal 中的get()
方法,关键的 map 是在 Thread 类中的threadLocals
变量,让咱们继续看看 ThreadLocalMap 的源代码:less
ThreadLocal.ThreadLocalMap threadLocals = null;
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
// 使用ThreadLocal做为key,而且是弱引用
super(k);
value = v;
}
}
// 省略代码
}复制代码
根据上一篇文章所述,若是一个对象只有弱引用
,那么当下一次 GC 进行时,该对象就会被回收。那么让咱们整理一下:ide
弱引用
。 但须要注意的是,Entry 中,只有key是弱引用,但 value 依旧是强引用。那会不会出现 key 被垃圾回收后,这个 map 的 key 为 null,但 value 依旧存在的状况呢?工具
确实是有可能的,但 JDK 自己也作了优化,能够看看 ThreadLocalMap 的 set()方法:优化
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}复制代码
调用 set()的时候,ThreadLocalMap 检查到 key 为 null 的 entry 时,会将 value 也设置为 null,这样 value 以前对应的实例也能够被回收。
先让咱们看一个简单的例子:
public class ThreadLocalSimpleDemo {
public static void main(String[] args) {
int threads = 3;
InnerClass innerClass = new InnerClass();
for (int i = 1; i <= threads; i++) {
new Thread(() -> {
for (int j = 0; j < 4; j++) {
innerClass.add(String.valueOf(j));
innerClass.print();
}
innerClass.set("hello world");
}, "thread - " + i).start();
}
}
private static class InnerClass {
/**
* 添加
*/
public void add(String newStr) {
StringBuilder str = Counter.counter.get();
Counter.counter.set(str.append(newStr));
}
/**
* 打印
*/
public void print() {
System.out.printf(
"Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
Thread.currentThread().getName(),
Counter.counter.hashCode(),
Counter.counter.get().hashCode(),
Counter.counter.get().toString()
);
}
/**
* 赋值
*/
public void set(String words) {
Counter.counter.set(new StringBuilder(words));
System.out.printf(
"Set, Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
Thread.currentThread().getName(),
Counter.counter.hashCode(),
Counter.counter.get().hashCode(),
Counter.counter.get().toString()
);
}
}
private static class Counter {
/**
* 初始化时是一个空的StringBuilder对象
*/
private static ThreadLocal<StringBuilder> counter = ThreadLocal.withInitial(StringBuilder::new);
}
}复制代码
其打印结果为:
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:01
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:0123
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:0
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:012
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:0123
Set, Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:820066274, Value:hello world
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:012
Set, Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:155293473, Value:hello world
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:0123
Set, Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:1804272849, Value:hello world复制代码
能够看出,咱们在使用 ThreadLocal 时,用的是同一个对象,但各个线程对应的实例是不同的。而在调用 set() 方法后,对应的实例会被替换。
对于 Java Web 应用而言,Session 保存了不少信息。不少时候须要经过 Session 获取信息,有些时候又须要修改 Session 的信息。一方面,须要保证每一个线程有本身单独的 Session 实例。另外一方面,因为不少地方都须要操做 Session,存在多方法共享 Session 的需求。使用 ThreadLocal 进行实现:
public class SessionHandler {
public static ThreadLocal<Session> session = ThreadLocal.<Session>withInitial(() -> new Session());
@Data
public static class Session {
private String id;
private String user;
private String status;
}
public String getUser() {
return session.get().getUser();
}
public String getStatus() {
return session.get().getStatus();
}
public void setStatus(String status) {
session.get().setStatus(status);
}
public static void main(String[] args) {
new Thread(() -> {
SessionHandler handler = new SessionHandler();
handler.getStatus();
handler.getUser();
handler.setStatus("close");
handler.getStatus();
}).start();
}
}复制代码
ThreadLocal 使用起来虽然简单,但考虑到其设计确实很精巧,值得了解一下。
有兴趣的话能够访问个人博客或者关注个人公众号、头条号,说不定会有意外的惊喜。