ThreadLocal 始于JDK1.2,提供了一个操作线程对象变量(ThreadLocal.ThreadLocalMap)的方法,进而达到线程安全和数据与线程绑定的目的。
使用示例:
import org.junit.Test;
import java.util.concurrent.TimeUnit;
/**
* ThreadLocal类使用测试
*/
public class ThreadLocalTest {
public ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> Integer.valueOf(0));
@Test
public void test() throws InterruptedException {
ThreadLocalTest test = new ThreadLocalTest();
new Thread(()->{
while (true){
System.out.println(Thread.currentThread().getName()+"---"+test.nextCount());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
while (true){
System.out.println(Thread.currentThread().getName()+"---"+test.nextCount());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
while (true){
System.out.println(Thread.currentThread().getName()+"---"+test.nextCount());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
while (true){
System.out.println(Thread.currentThread().getName()+"---"+test.nextCount());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
TimeUnit.DAYS.sleep(1);
}
private Integer nextCount() {
Integer next = count.get();
next++;
count.set(next);
return next;
}
}
执行结果:
结果分析:
每个线程都有自己独有的count变量,互相不影响,无论更新还是查看,不存在线程安全问题。
源码分析:
主要是通过两个方法实现: count.get() 和 count.set(T t);
count.get()源码如下:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
//获取到当前线程
Thread t = Thread.currentThread();
//用当前线程获取到一个ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
//如果当前线程可以获取到ThreadLocalMap实例
if (map != null) {
//通过当前ThreadLocal对象,从刚才Thread中获取的ThreadLocalMap中获取一个Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//最终数据存储在Entry的value字段里
T result = (T)e.value;
return result;
}
}
//如果当前线程获取不到一个ThreadLocalMap对象,则为当前线程初始化一个
return setInitialValue();
}
分析:首先count是ThreadLocal类的实例,所有调用的是ThreadLocal里面的get()方法。具体的分析在源码注释里面
问题1:如何通过当前线程获取到ThreadLocalMap对象?
源码如下:
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
分析:在java.lang.Thread中有一个成员变量,引用了ThreadLocal.ThreadLocalMap,所以相当于获取当前线程的成员变量的值
问题2:当前的变量值到底是怎么存储的?
一张图解决:
值存储在Thread类中的一个成员变量中(ThreadLocal.ThreadLocalMap)。这个成员变量是一个k,v结构的类似map的类实例。
key是ThreadLocal对象,也就是实例代码中的count,Map中是用private Entry[] table;存储数据的。也就是一个线程的map实例中可以存储多个k,v。
value是当前的业务set的对象,也就是实例代码中count.set(T t)的变量。
注意:
1.ThreadLocal是一个弱引用,在内存不足时,JVM会考虑回收掉,所以有可能在内存不足时线程绑定的变量有可能会被回收,也就是线程成员变量ThreadLocal.ThreadLocalMap这个map的k指向了一个被回收了的地址,即k是null了。导致缓存在线程本地的变量丢失。
2. ThreadLocal尽量不要和线程池混合使用,因为ThreadLocal是将变量保存到线程变量了,如果和线程池混合使用,那么这个线程不会被销毁,会被回收到线程池中分配给下一个任务,所以下一个任务拿到的新线程有可能有之前线程缓存的线程变量。
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
-
点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
-
关注公众号 『逆行的碎石机』,不定期分享原创知识。
-
同时可以期待后续文章ing🚀