ThreadLocal是什么?
ThreadLocal会给每个线程提供一个变量副本, 把共享变量的数据都拷贝到自己的变量副本中, 每个线程都只操作自己变量副本中的变量, 不会操作到主内存中的共享变量. 这样可以防止在并发的情况下,因其他线程对共享变量的修改而对当前线程造成影响.
ThreadLocal的应用场景
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。而这种情况下也可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等。
package day4_4;
public class TestThreadLocal {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName() + "线程");
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final TestThreadLocal test = new TestThreadLocal();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread() {
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
}
;
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
执行结果:
可以看到,每个线程的ThreadLocal类型变量是不一样的.
ThreadLocal怎么实现每个线程保存的变量数据不一样呢?
从源码中看到:
// ThreadLocal的set方法
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 保存到Thread类中的threadLocals 中,键为ThreadLocal对象,值为ThreadLocal对应的值
map.set(this, value);
else
createMap(t, value);
}
// 获取Thread类中的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 每个线程都有一个ThreadLocal.ThreadLocalMap类的threadLocals 变量
ThreadLocal.ThreadLocalMap threadLocals = null;
从源码中可以看出,ThreadLocal类型的变量在保存时实际是保存在Thread类的threadLocals 变量中的, threadLocals 是一个ThreadLocalMap类, 保存的是ThreadLocal对象和对应的值的键值对. 那么, 为什么键是ThreadLocal对象呢?因为每个线程中可以有多个ThreadLocal类型的变量, 所以要将ThreadLocal对象当做键才能在执行get方法时获取到对应的值.
ThreadLocal的get方法
public T get() {
Thread t = Thread.currentThread();
//获取当前线程的threadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
// 根据ThreadLocal对象this从threadLocals 中获取对应的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
get方法也是根据当前的ThreadLocal对象从当前线程的threadLocals 中获取值
ThreadLocal的remove方法
//remove方法, 同理,从线程中的threadLocals 删除
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocal总结
在每个线程Thread类内部都有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
- 通过ThreadLocal创建的副本实际是存储在每个线程自己的threadLocals中的;
- 为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
- 每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。
- ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
- 适用于业务逻辑不依赖副本变量的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案
ThreadLocal问题:
1 ThreadLocal内存泄漏?, 强引用弱引用?, GC? .
2 ThreadLocalMap实现,解决哈希冲突的方法