什么是ThreadLocal
ThreadLocal是一个线程独享的本地存放空间,什么是独享的本地空间,也就是时候每个线程都有一份,
线程之间不能共享该区域。主要用于存储线程变量。
什么情况下会使用ThreadLocal
多线程的情况下,如果存在资源的竞争的时候,我们通常使用锁的机制去处理这种资源的竞争,而对于一些资源
每个线程都要有一份的情况下,我们就会使用ThreadLocal.
例如:
- Spring的事务管理,是通过C3P0或者其他的数据库连接池中获取一个Connection,然后将Connection存放每个线程中
的ThreadLocal中,保证每个线程之间使用的数据库连接都是独享的。我们可以想下,如果每个线程都是使用同一个
数据连接,我们就无法控制事务的提交和回滚。因此每个线程必须独占这个数据库连接。那如果我们在Dao层
获取Connection,然后执行SQL提交事务。就不会存在这个问题啦,其实并不是,我们通常会在三层中的服务层进行事务
的开启和事务的提交、回滚。那么为什么会在服务层进行事务的处理,主要是因为事务的边界问题。
因为Service会调用一系列的DAO对数据库进行多次操作。多个Dao中我们无法确定到底哪个Dao进行事务的提交。
因此使用ThreadLocal进行存放每个数据库的连接。 - 还有一种场景,在多数据源的情况下,我们可以通过一个key获取对应的数据源,而这个key值
,从我们ThreadLocal中获取到对应的数据源。从而隔离多线程下数据源的访问。
ThreadLocal的使用
- ThreadLocal的使用,我们只需要创建一个ThreadLocal并且指定存放的数据类型即可。
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
- ThreadLocal类的方法
public T get() 获取当前线程的变量
public void set(T value) 设置当前线程的变量
public void remove() 删除线程变量
ThreadLocal源码分析
一、ThreadLocal结构分析
ThreadLocal的实现原理,我们可以通过ThreadLocal的get方法进行跟踪分析。
1 public T get() {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null) {
5 ThreadLocalMap.Entry e = map.getEntry(this);
6 if (e != null) {
7 @SuppressWarnings("unchecked")
8 T result = (T)e.value;
9 return result;
10 }
11 }
12 return setInitialValue();
13 }
从ThreadLocal的get方法中我们可以看出,现获取当前的线程,再通过线程去获取
对应线程的ThreadLocalMap.从ThreadLocalMap的名字,我们可以看出他是一个Map的数据结构。
我们就想ThreadLocalMap是存放在哪里的呢?我们可以通过跟中getMap的方法中看出,ThreadLocalMap
是存放到Thread中。
1 ThreadLocalMap getMap(Thread t) {
2 return t.threadLocals;
3 }
而Thread中的ThreadLocalMap是在ThreadLocal中进行初始化。我可以通过ThreadLocal中的set方法和setInitialValue方法查看到初始化ThreadLocalMap.
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
二、ThreadLocalMap
- 1.存储结构分析
我可以从ThreadLocalMap的源码进行查看,ThreadLocalMap跟HashMap等都是使用Entry数据结构进行存储.
1 static class ThreadLocalMap {
2 static class Entry extends WeakReference<ThreadLocal<?>> {
3 /** The value associated with this ThreadLocal. */
4 Object value;
5
6 Entry(ThreadLocal<?> k, Object v) {
7 super(k);
8 value = v;
9 }
10 }
11
12 private Entry[] table;
那么如果我们在代码中创建两个ThreadLocal会怎样存储的呢?
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1;
}
};
public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "hello";
}
};
通过对setInitialValue方法进行分析发现,我们创建两个ThreadLocal,其实内部是将当前的ThreadLocal对象作为键值,initialValue的返回值做为Value。
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;
}
总结:
我们通过对ThreadLocalMap的set和get方法,可以发现,我们ThreadLocalMap是存Thread中,由ThreadLocal的设置相关方法进行创建。存储的数据结构是以ThreadLocal作为键保存对应的Value到ThreadLocalMap中。
我们可以通过源码查看到Entry是Key是一个弱引用,而不是像HashMap那样使用强引用。那么ThreadLocalMap中的key为弱引用的,那么ThreadLcaoMap的中的值只能存活到下一次的GC。
弱引用:通常用于存储非必需对象的,弱引用指向的对象实例,在下一次垃圾收集发生之前是存活的。
- ThreadLocalMap内存泄漏
从存储的结构我们可以发现,ThreadLocal是ThreadLocalMap的键值。如果在我们的代码中将 ThreadLocalRef设置为null的情况下,我们就不可以通过ThreadLocal进行访问,而ThreadLocalMap还是会保存Entry,而这样就会造成内存的泄漏。Entry只能等待线程结束后才能进行GC。
- ThreadLocalMap内存泄漏
import org.junit.Test;
public class UseThreadLocal {
private static int threadCount = 30;
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1;
}
};
public static ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "hello";
}
};
class ThreadLocalRunable implements Runnable {
private int threadId;
public ThreadLocalRunable(int threadId) {
this.threadId = threadId;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id = "+threadLocal.get());
threadLocal2 = null;
Integer threadLocalInt = threadLocal.get();
threadLocal.set(threadLocalInt+this.threadId);
System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
}
}
@Test
public void test() throws InterruptedException {
Thread[] threads = new Thread[threadCount];
for (int i = 0 ;i<threadCount;i++){
threads[i] = new Thread(new ThreadLocalRunable(i),"线程"+i);
}
for (Thread t :threads){
t.start();
t.join();
}
}
}
为了防止内存泄漏,我们可以通过ThreadLocal提供的remove移除ThreadlocalMap中ThreadLocal的Value。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
3.为什么ThreadLocalMap中的ThreadLocal使用弱引用
上面我们说过当我们为ThreadLocalRef设置为null的时候,ThreadLocal会被回收,而ThreadLocal被调用的前提是没有被其他引用。那么在ThreadLocalMap中会将ThreadLocal作为键值。如果ThreadLocalMap中使用强引用的话,ThreadLocal就一定不会被回收,就会造成ThreadLocal的内存泄漏,但是ThreadLocalMap使用的是弱引用,那也就是说在下一次GC的时候ThreadLocal就会被回收,而不会造成内存泄漏。- ThreadLocalMap内存溢出
我们使用多线程是为了提高CPU的使用效率或者将一些耗时的工作由其他线程进行处理。
那么如果我们线程处理的时间非常耗时,存放在ThreadLocal中的数据非常大,并且存放在Threadlocal中的数据没有释放。JVM为了避免内存溢出,
- ThreadLocalMap内存溢出
运行时设置堆内存为1m
-Xms=1m
-Xmx=1m
public class ThreadLocalOOM {
private static int threadCount = 30;
public static ThreadLocal<HashMap> threadLocal = new ThreadLocal<HashMap>() {
@Override
protected HashMap initialValue(){
HashMap map= new HashMap<>();
for (int i=0;i<1000000000;i++){
map.put(i,i);
}
return map;
}
};
class ThreadLocalRunable implements Runnable {
private int threadId;
public ThreadLocalRunable(int threadId) {
this.threadId = threadId;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":start"+"thread id = "+threadLocal.get());
System.out.println(Thread.currentThread().getName() + "-" + Thread.currentThread().getId() + ":end" + " thread id=" + threadLocal.get());
}
}
@Test
public void test() throws InterruptedException {
Thread[] threads = new Thread[threadCount];
for (int i = 0 ;i<threadCount;i++){
threads[i] = new Thread(new ThreadLocalRunable(i),"线程"+i);
}
for (Thread t :threads){
t.start();
t.join();
}
}
}