有关Java中ThreadLocal相关内容的总结!
ThreadLocal
多个线程访问同一个共享变量时特别容易出现并发问题, 特别是在多个线程同时需要对一个共享变量进行写入时! 为了保证线程安全, 一般使用者在访问共享变量时需要进行适当的同步.
同步的一般措施为加锁. 但是加锁机制较为复杂, 有没有一种方法可以做到:当创建一个变量之后, 每一个线程进行访问时, 访问的是自己线程的变量. 此方法即为ThreadLocal变量!
ThreadLocal由JDK包提供, 提供了线程本地变量, 即:

当你创建了一个ThreadLocal变量, 那么访问这个变量的每一个线程都会有这个变量的一个副本. 当多个线程操作这个变量的时候, 每一个线程操作的都是自己本地内存中的变量. 避免了线程安全问题! 创建一个ThreadLocal后, 每一个线程都会复制一个变量到自己的本地内存.
1. 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 32 33 34 35 36 37 38 39 40 41 42
| package club.jasonkayzk666.chapter1.lesson11.threadlocal;
public class ThreadLocalTest {
public static ThreadLocal<String> localVariable = new ThreadLocal<>();
public static void print(String str) { System.out.println(str + ": " + localVariable.get());
localVariable.remove(); }
public static void main(String[] args) { Thread threadOne = new Thread(new Runnable() { @Override public void run() { localVariable.set("thread-one local variable"); print("thread-one");
System.out.println("thread-one remove after: " + localVariable.get()); } });
Thread threadTwo = new Thread(new Runnable() { @Override public void run() { localVariable.set("thread-two local variable"); print("thread-two");
System.out.println("thread-two remove after: " + localVariable.get()); } });
threadOne.start(); threadTwo.start(); }
}
|
代码中, 线程One通过set方法设置了localVariable的值, *这其实是线程One本地内存中的一个副本! 这个副本线程Two是无法访问的! *
所以输出为:
1 2 3 4
| thread-two: thread-two local variable thread-two remove after: null thread-one: thread-one local variable thread-one remove after: null
|
2. ThreadLocal的实现原理
下图为ThreadLocal相关类的类图结构.

在图中可知:
Thread类中有一个threadLocals和一个inheritableThreadLocals, 都是ThreadLocalMap类型的变量, 而ThreadLocalMap是一个定制化的HashMap.
- 默认情况下, 每个线程中的两个变量都为
null, 只有当前线程第一次调用ThreadLocal的set或者get方法时才会创建他们.
- 每个线程的本地变量实际上并不是存放在
ThreadLocal实例中! 而是存放在调用线程的threadLocals里面, 并存放起来. 当调用线程调用它的get方法时, 再从当前线程的threadLocals变量中将其拿出来使用!
- 如果调用线程一直不终止, 那么这个变量会一直存放在调用线程的
threadLocals变量里面, 占用内存!
- 所以当不再使用本地变量时, 可以通过
ThreadLocal变量的remove方法, 从当前线程的threadLocals里面删除该本地变量!
ThreadLocal中的threadLocals被设计为map的原因: 每个线程可以关联多个ThreadVariable变量!
1): void set(T value)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { map.set(this, value); } else { createMap(t, value); } }
|
下面再看看createMap(t, value)
1 2 3
| void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
|
即创建了一个当前线程的threadLocals变量!
2): T get()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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 setInitiaVaule(); }
|
对于setInitialValue()的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; }
protected T initialValue() { return null; }
|
当前ThreadLocal变量不为空则设置为null, 否则调用createMap方法创建线程变量.
3): void remove()
1 2 3 4 5 6
| public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) { m.remove(this); } }
|
如果当前线程的ThreadLocal变量不为空, 则删除.
4): 总结:
- 每个线程内部都有一个名为
threadLocals的成员变量, 变量类型为HashMap, 其中key为定义的ThreadLocal的this引用, value为使用set设置的值!
- 每个线程的本地变量存放在线程自己的内存变量
threadLocals变量中, 若线程一直不消亡, 本地变量一直存在, 可能造成内存溢出. *所以使用完毕要调用ThreadLocal的remove方法删除本地变量!
3. 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
| package club.jasonkayzk666.chapter1.lesson11.threadlocal;
public class ThreadLocalNotInherible {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("hello world");
Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("thread: " + threadLocal.get()); } });
thread.start();
System.out.println("main: " + threadLocal.get()); }
}
|
输出为:
1 2
| main: hello world thread: null
|
本例中创建了一个ThreadLocal变量并在父线程中设置了值hello world, 此值在子线程中无法获取! 这是对的!
但是有时想要子线程访问父线程中的值, 此时可以使用InheritableThreadLocal类
4. InheritableThreadLocal类
InheritableThreadLocal继承自ThreadLocal, 提供了一个特性即: 让子线程可以访问父线程中设置的本地变
1): InheritableThreadLocal的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class InheritableThreadLocal<T> extends ThreadLocal<T> { protected T childValue(T parentValue) { return parentValue; } ThreadLocalMap getMap(Thread t) { return t. inheritableThreadLocals; } void createMap(Thread t, T firstValue) { t. inheritableThreadLocal = new ThreadLocalMap(this, firstValue); } }
|
上述代码可以看出, InheritableThreadLocal继承了ThreadLocal, 并重写了三个方法;
InheritableThreadLocal重写了createMap方法:
当首次调用set方法时, 创建当前线程的inheritableThreadLocals变量实例, 而不再是threadLocals.
同时调用get方法时, 获取的也是inheritableThreadLocals而不是threadLocals.
下面看下如何让子线程可以访问到父线程的本地变量, 从创建Thread的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public Thread(Runnable target) { init(null ,target, "Thread-" + nextThreadNum(), 0); }
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { ..... Thread parent = currentThread(); ... if (parent. inheritableThreadLocals != null) { this. inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize; tid = nextThreadID(); } }
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
|
Thread创建的构造函数的源码如上: 在创建线程时, 构造函数会调用init方法. 可以看到, 在createInheritedMap内部使用父进程的inheritableThreadLocals变量, 然后赋予了子线程!
而ThreadLocalMap的构造函数将父线程的InheritableThreadLocal变量复制到新的LocalThreadMap对象中!
2): 总结:
InheritableThreadLocal类通过重新方法, 将本地变量保存到了inheritableThreadLocals里面. 线程在通过InheritableThreadLocal类实例的get或者set方法设置变量时会创建当前线程的inheritableThreadLocals变量. 当父线程创建子线程时, 构造函数会把父线程中的inheritableThreadLocals变量里面的本地变量复制一份给子线程.
*例: *
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package club.jasonkayzk666.chapter1.lesson11.threadlocal;
public class InheritableThreadLocalDemo {
private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) { inheritableThreadLocal.set("hello world");
Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("thread: " + inheritableThreadLocal.get()); } });
thread.start();
System.out.println("main: " + inheritableThreadLocal.get()); } }
|
输出为:
1 2
| main: hello world thread: hello world
|
3):InheritableThreadLocal使用场景
- 子线程需要使用存放在
threadLocal变量中的用户登录信息.
- 一些中间件把统一的id追踪的整个调用链路记录下来等