ThreadLocal简介
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题
我们先通过一个例子来看一下ThreadLocal的基本用法
package com.gpdi.operatingunit.test;
/**
* @description:
* @author: Lxq
* @date: 2020/1/14 11:24
*/
public class ThreadLocalTest {
static class TestThread extends Thread{
private static ThreadLocal<Integer> local = new ThreadLocal<>();
@Override
public void run() {
super.run();
for (int i = 0; i < 4 ; i++) {
local.set(i);
System.out.println(Thread.currentThread().getName() + "======" + local.get());
}
}
}
public static void main(String[] args) {
TestThread thread1 = new TestThread();
thread1.setName("线程一");
TestThread thread2 = new TestThread();
thread2.setName("线程二");
thread1.start();
thread2.start();
}
}
线程二======0
线程一======0
线程二======1
线程一======1
线程二======2
线程一======2
线程二======3
线程一======3
两个线程都在向threadLocal对象中set()数据值,但每个线程都还是能取出自己设置的数据,确实可以达到隔离线程变量的效果
ThreadLocal的实现原理
1、set方法源码
public void set(T value) {
//(1)获取当前线程(调用者线程)
Thread t = Thread.currentThread();
//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this, value);
//(4)如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
(2)处调用getMap方法获得当前线程对应的threadLocals,该方法代码如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}
如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法,这个时候就需要调用createMap方法创建threadLocals,该方法如下所示
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。
2、get方法源码
在get()方法实现中,首先获取当前调用这线程,如果线程的threadLocals不是null,那就直接返回 当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似set()方法实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量,否则创建threadLocals变量,同样添加的值为null
public T get() {
//(1)获取当前线程
Thread t = Thread.currentThread();
//(2)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
return setInitialValue();
}
private T setInitialValue() {
//protected T initialValue() {return null;}
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this, value);
//如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
return value;
}
3、remove方法的实现
remove方法是判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中 指定的threadLocals变量
public void remove() {
//获取当前线程绑定的threadLocals
ThreadLocalMap m = getMap(Thread.currentThread());
//如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
if (m != null)
m.remove(this);
}
备注:
每个线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。
ThreadLocal不支持继承性
同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)
package com.gpdi.operatingunit.test;
/**
* @description:
* @author: Lxq
* @date: 2020/1/14 11:24
*/
public class ThreadLocalTest {
/**
* 创建ThreadLocal变量
*/
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("主线程");
// 新建一个子线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程中的本地变量" + threadLocal.get());
}
});
thread.start();
System.out.println("主线程中的本地变量值:"+threadLocal.get());
}
}
主线程中的本地变量值:主线程
子线程中的本地变量null
InheritableThreadLocal类
前面说ThreadLocal类无法继承,InheritableThreadLocal类就能解决不能继承问题
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.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
package com.gpdi.operatingunit.test;
/**
* @description:
* @author: Lxq
* @date: 2020/1/14 11:24
*/
public class ThreadLocalTest {
/**
* 创建ThreadLocal变量
*/
public static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("主线程");
// 新建一个子线程
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程中的本地变量:" + threadLocal.get());
}
});
thread.start();
System.out.println("主线程中的本地变量值:"+threadLocal.get());
}
}
主线程中的本地变量值:主线程
子线程中的本地变量:主线程