ThreadLocal 概述
- 当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
- 当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。
- ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
使用场景:如为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B 线程正在使用的 Connection。还有 Session 管理等问题。
set方法
ThreadLocal为每个线程都创建一个map对象。从而保证线程隔离。
public void set(T value) { // T value , 泛型实现,可以 set 任何对象类型
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
get方法
看这个方法,我们能够想到如果是map时,get 的两次结果应该是"Hello World" 和"World is beautiful".但结果却是。。。。
public class DemoTest{
public static void main(String[] args){
ThreadLocal<String> localVariable = new ThreadLocal<> () ;
localVariable.set("Hello World");
localVariable.set("World is beautiful");
System.out.println(localVariable.get());
System.out.println(localVariable.get());
}
}
探究解析:从程序中来看,我们进行了两次 set 方法的使用。
第一次 set 的值为 Hello World ;第二次 set 的值为 World is beautiful。接下来我们进行了两次打印输出 get 方法,那么这两次打印输出的结果都会是 World is beautiful。 原因在于第二次 set 的值覆盖了第一次 set 的值,所以只能 get 到 World is beautiful。
总结:ThreadLocal 中只能设置一个变量值,因为多次 set 变量的值会覆盖前一次 set 的值,我们之前提出过,ThreadLocal 其实是使用 ThreadLocalMap 进行的 value 存储,那么多次设置会覆盖之前的 value,这是 get 方法无需入参的原因,因为只有一个变量值。
remove方法
public class DemoTest{
public static void main(String[] args){
ThreadLocal<String> localVariable = new ThreadLocal<> () ;
localVariable.set("Hello World");
System.out.println(localVariable.get());
localVariable.remove();
System.out.println(localVariable.get());
}
}
Tips:remove 方法同 get 方法一样,是没有任何入参的,因为 ThreadLocal 中只能存储一个变量值,那么 remove 方法会直接清除这个变量值
上面代码运行的结果,如下图所示。
多线程下的 ThreadLocal
场景设计:
- 创建一个全局的静态 ThreadLocal 变量,存储 String 类型变量;
- 创建两个线程,分别为 threadOne 和 threadTwo;
- threadOne 进行 set 方法设置,设置完成后沉睡 5000 毫秒,苏醒后进行 get 方法打印;
- threadTwo 进行 set 方法设置,设置完成后直接 get 方法打印,打印完成后调用 remove 方法,并打印 remove 方法调用完毕语句;
- 开启线程 threadOne 和 threadTwo ;
- 执行程序,并观察打印结果。
package jvm.juc;
public class ThreadLocalTest {
static ThreadLocal<String> local = new ThreadLocal<>();
public static void main(String[] args) {
Thread threadA = new Thread(()->{
local.set("线程One-在里面设置值了");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(local.get());
});
threadA.setName("ThreadOne");
Thread threadB = new Thread(()->{
local.set("线程Two-在里面设置值了");
System.out.println(local.get());
local.remove();
});
threadB.setName("ThreadTwo");
threadA.start();
threadB.start();
}
}
从以上结果来看,在 threadTwo 执行完 remove 方法后,threadOne 仍然能够成功打印,这更加证明了 ThreadLocal 的专属特性,线程独有数据,其他线程不可侵犯。
总结
ThreadLocal 是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal 比直接使用 synchronized 同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性,但也有缺点是只能存储一个值。