转载本文章请标明作者和出处
加油,程序猿!!!
ThreadLocal
作用
多线程访问同一个变量的时候会有线程安全的问题,ThreadLocal会把每一个线程访问的变量变成这个线程私有的变量,从而避免了线程安全的问题。
使用代码
ps: 其中线程休眠,是为了让其他线程有机会在赋值之后再操作ThreadLocal;
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
for (int x = 1; x <= 5; x++) {
final int index = x;
Thread thread = new Thread(() -> {
threadLocal.set(index);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : " + threadLocal.get());
threadLocal.remove();
});
thread.start();
}
}
}
- 运行结果:
可以验证,ThreadLocal保证了变量是被每一个线程肚子拥有的,从而避免了线程安全的问题。
原理
- 下面是源码中ThreadLocal中的set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我们可以看到他用当前线程做key获取到了一个map,而这个map就是Thread线程类本身维护的一个变量
所以,其实,ThreadLocal的变量是存储在每个线程本身的threadLocals map变量中,这样也是很合理的,每个线程都有自己的容器来存储自己线程特有的变量,而这个map的key就是ThreadLocal的引用this,这样就可以保证,每个线程可以存储多个ThreadLocal的变量。
当然这个threadLocals不是在县城创建的时候初始化的,是在ThreadLocal第一次get或者set的时候检测为空的话就会进行初始化。
- 下面是源码中ThreadLocal中的get方法
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();
}
- 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);
return value;
}
如果map不为空的话,那么就用ThreadLocal的引用this,去拿当前ThreadLocal存储的值,如果这时候线程中的threadLocals变量还没有初始化就要走setInitialValue方法。这个方法,会将threadLocals变量进行初始化,并且用ThreadLocal的引用this当key,null做值,进行一个初始化,并返回null。
- remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
如果使用了ThreadLocal向当前的线程里面set了值的话,如果线程不结束,这个值是不会消失的,如果你想删除这个值,就可以调用remove方法,其实就是把这个Entry从map中删除而已。
ThreadLocal的问题
不支持继承性
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(111);
new Thread(() -> System.out.println(threadLocal.get())).start();
}
}
上面这理所当然的取不出来值,打印结果为null,因为子线程的threadLocals并没有存主线程的值,而要想解决这个问题,就需要认识一个新的工具InheritableThreadLocal。
InheritableThreadLocal
上文说了,InheritableThreadLocal是为了解决ThreadLocal不支持继承性而产生的,那我们先看一下使用了InheritableThreadLocal的效果。
public class ThreadLocalTest {
public static void main(String[] args) {
InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
threadLocal.set(111);
new Thread(() -> System.out.println(threadLocal.get())).start();
}
}
- 运行结果
果不其然,我们获取到了主线程set到的111。
原理解析
打开InheritableThreadLocal类的源码,我们可以看到他是继承了ThreadLocal类,并且仅仅重写了三个方法
其中inheritableThreadLocals肯定引起了你的注意,这个变量和threadLocals变量一样,也是每一个线程私有的一个map,name这个的作用是什么的?为什么InheritableThreadLocals将返回的map变成了InheritableThreadLocals就可以实现继承性了呢,我们看一下Thread类的源码。我们在线程的初始化方法init中看到了
如果父线程的InheritableThreadLocals不为空,就会复制给子线程,这样,父线程的变量当然就可以被子线程看到啦,因为这里进行了同步,所以inheritableThreadLocals可以实现线程的继承性