1、ThreadLocal简介
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景
2、ThreadLocal与Synchronized的区别
ThreadLocal<T>实际上是一个与线程绑定的变量。ThreadLocal与Synchronized都能用来解决多线程并发的问题。
- Synchronized用于线程数据共享,而ThreadLocal用于数据隔离。
- Synchronized使用锁的机制使得代码块或变量在某一时间内只有被一个线程访问,而ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间内访问到的并不是同一个对象,隔离了多个线程对数据的数据共享,而Synchronized则是在多线程通信时能够获得数据共享
3、ThreadLocal的简单使用
public class ThreadLocalDemo {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
static void print(String str){
System.out.println(str+":"+threadLocal.get());
threadLocal.remove();
};
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("Local_A");
print("A");
System.out.println("After Remove :"+threadLocal.get());
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("Local_B");
print("B");
System.out.println("After Remove :"+threadLocal.get());
}
},"B").start();
}
}
B:Local_B
After Remove :null
A:Local_A
After Remove :null
4、ThreadLocal的原理
翻阅源码我们能看到
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取线程中的属性threadLocalMap ,如果threadLocalMap为空
//创建threadLocalMap,并赋值,如果thradLoaclMap不为空,则直接更新变量的值
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
//初始化,threadLocalMap并赋值
createMap(t, value);
}
}
从上边的源码能看出ThreadLocal set赋值时首先会获得当前线程thread,并获取线程中的ThreadLocalMap属性。如果map属性不为空,直接更新value,如果为空,则实例化threadLocalMap并初始化value
关于ThreadLocalMap和createMap的原理
我们可以进一步查看源码
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 是Thread Local的静态内部类,主要是用Entry来进行保存数据,还是弱引用继承。在Entry内部是使用ThreadLocal来作为key,我们设置(set)的value当做value
//thradLocal的内部实现方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//ThreadLocal构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
ThreadLocal的get方法
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获得当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//看map是否为空
if (map != null) {
//获取ThreadLocalMap中存储的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果为空,初始化map,初始化的结果是ThreadLocalMap的key为ThreadLocal,value 为空
return setInitialValue();
}
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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
ThreadLocal的remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
ThreadLocal与Thread Local Map 和Thread的区别
1、Thread线程内部都有一个Map(ThreadLocalMap)
2、Map里存储对象ThreadLocal(key)和线程的副本变量(value)
3、Thread内部的map是由ThreadLocal维护的,由ThreadLocal向map获取和设置线程的参数
4、对于不同的Thread,每次获取副本参数时,别的副本不能获取当前副本值,因而形成了副本的隔离,使线程互不干扰
Thread可以拥有多个ThreadLoacl来维护自己的线程独享的共享变量
5、ThreadLocal的使用场景
根据上述情况我们能看出,当我们使用ThreadLocal的场景
1、每个线程都需要有自己单独的实例
2、实例需要在多个方法中共享,但是不在多个线程中共享