一、ThreadLocal简介
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
二、ThreadLocal的实现原理
下面是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。
三、ThreadLocal简单使用
以常用的ThreadLocal作动态切换数据源的使用为例子
1. 新建一个DatabaseContextHolder类
package com.orm.mybatis.dynamic.config;
/**
* 自定义数据源切换类
*/
public class DatabaseContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDBKey(String dataSourceKey) {
contextHolder.set(dataSourceKey);
}
public static String getDBKey() {
return contextHolder.get();
}
public static void clearDBKey() {
contextHolder.remove();
}
}
ThreadLocal<String>测试例子:
下面的例子中,先在主线程设置setDBKey(“andrew”+6666),再开启三个子线程,在每个线程开始前先打印本地变量的值,看看是否受到主线程设值andrew6666的影响,然后再内部设置了andrew的1-999的随机值,然后调用随机1-999的毫秒的睡眠时间,再打印当前本地变量的值。如果该线程睡眠后的变量的值和睡眠前设置的值一致,则不受其他线程的影响。代码如下所示。
@Test
public void contextLoads() throws InterruptedException {
DatabaseContextHolder.setDBKey("andrew"+6666);
for (int i = 0; i <3 ; i++) {
new Thread(() -> {
Random random = new Random();
int k = random.nextInt(999);
System.out.println(Thread.currentThread().getName()+"设置前:"+DatabaseContextHolder.getDBKey());
DatabaseContextHolder.setDBKey("andrew"+k);
System.out.println(Thread.currentThread().getName()+"设置了:"+"andrew"+k);
int h = random.nextInt(999);
System.out.println(Thread.currentThread().getName()+"的睡眠时间"+h);
try {
Thread.sleep(h);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"睡眠后:"+DatabaseContextHolder.getDBKey());
}).start();
}
Thread.sleep(3000);
System.out.println("------------------------------------------");
System.out.println(Thread.currentThread().getName()+"线程"+DatabaseContextHolder.getDBKey());
}
输出结果为
Thread-2设置前:null
Thread-2设置了:andrew817
Thread-2的睡眠时间746
Thread-4设置前:null
Thread-4设置了:andrew514
Thread-4的睡眠时间881
Thread-3设置前:null
Thread-3设置了:andrew717
Thread-3的睡眠时间373
Thread-3睡眠后:andrew717
Thread-2睡眠后:andrew817
Thread-4睡眠后:andrew514
------------------------------------------
main线程andrew6666
Process finished with exit code 0
多线程睡眠后的变量的值和睡眠前设置的值一致,互相独立,不受其他线程的影响。
四、static String简单使用
以常用的static String作动态切换数据源的使用为例子
1. 新建一个DatabaseContextHolderTest类
package com.orm.mybatis.dynamic.config;
/**
* 自定义数据源切换类
*/
public class DatabaseContextHolderTest {
private static String test;
public static void setDBKey(String dataSourceKey) {
test=dataSourceKey;
}
public static String getDBKey() {
return test;
}
public static void clearDBKey() {
test = null;
}
}
static String测试例子:
@Test
public void contextLoadsTest1() throws InterruptedException {
DatabaseContextHolderTest.setDBKey("andrew"+6666);
for (int i = 0; i <3 ; i++) {
new Thread(() -> {
Random random = new Random();
int k = random.nextInt(999);
System.out.println(Thread.currentThread().getName()+"设置前:"+DatabaseContextHolderTest.getDBKey());
DatabaseContextHolderTest.setDBKey("andrew"+k);
System.out.println(Thread.currentThread().getName()+"设置了:"+"andrew"+k);
int h = random.nextInt(999);
System.out.println("睡眠时间"+h);
try {
Thread.sleep(h);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"睡眠后:"+DatabaseContextHolderTest.getDBKey());
}).start();
}
Thread.sleep(3000);
System.out.println("------------------------------------------");
System.out.println(Thread.currentThread().getName()+"线程"+DatabaseContextHolder.getDBKey());
}
输出结果为
Thread-3设置前:andrew6666
Thread-3设置了:andrew780
睡眠时间727
Thread-2设置前:andrew780
Thread-2设置了:andrew546
睡眠时间6
Thread-4设置前:andrew546
Thread-4设置了:andrew48
睡眠时间266
Thread-2睡眠后:andrew48
Thread-4睡眠后:andrew48
Thread-3睡眠后:andrew48
/------------------------------------------
main线程andrew48
Process finished with exit code 0
多线程睡眠后的变量的值和睡眠前设置的值不一致,为线程最新修改的值,static的对象为多线程共用,互相影响。
五、ThreadLocal<String>和static String的区别
ThreadLocal<String>:多线程睡眠后的变量的值和睡眠前设置的值一致,互相独立,不受其他线程的影响。
static String:多线程睡眠后的变量的值和睡眠前设置的值不一致,为线程最新修改的值,static的对象为多线程共用,互相影响。