作为一个稀有的Java妹子,所写的所有博客都只是当作自己的笔记,留下证据自己之前是有用心学习的~哈哈哈哈(如果有不对的地方,也请大家指出,不要悄悄咪咪的不告诉我)
ThreadLocal
ThreadLocal类主要是为了解决多线程并发,出现线程共用变量的问题。与synchronized等同步方法不同的是,synchronized是采取线程排队的方式,使得同一时间点只有一个线程访问非线程安全的变量;而ThreadLocal是为每个线程创建非线程安全变量的副本,线程之间无法交叉访问变量,从而解决并发问题。
ThreadLocal支持泛型变量,在类中初始化如下:
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>()
1.几个重要的方法
//初始化方法,默认返回null
protected T initialValue() {
return null;
}
//set方法,给本地变量赋值,其实是用的ThreadLocalMap内部类来保存的
//将当前的线程对象作为key,传进来的value作为值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//获取本地变量
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
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;
}
//移除当前的本地变量
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
主要就是以上四个方法,ThreadLocal通过ThreadLocalMap内部类,达到为每个线程创建一个变量的副本,使得每个线程之间互不干扰的访问非线程安全的变量。简单的理解就是ThreadLocal通过map以线程对象为key,变量值为value,线程只能修改获取它自己线程对应的变量。
2.简单例子
public class ThreadLocalDemo {
//重写initialValue方法
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
//获取的时候将值递增1
public Integer getValue(){
threadLocal.set(threadLocal.get()+1);
return threadLocal.get();
}
public static void main(String[] args) {
ThreadDemo thread1 = new ThreadDemo(new ThreadLocalDemo());
ThreadDemo thread2 = new ThreadDemo(new ThreadLocalDemo());
ThreadDemo thread3 = new ThreadDemo(new ThreadLocalDemo());
thread1.run();
thread2.run();
thread3.run();
}
}
class ThreadDemo extends Thread{
private ThreadLocalDemo threadLocalDemo;
public ThreadDemo(ThreadLocalDemo threadLocalDemo){
this.threadLocalDemo = threadLocalDemo;
}
@Override
public void run() {
for(int i=0;i<3;i++){
System.out.println(this.getName()+":"+this.threadLocalDemo.getValue());
}
}
}
结果:可以看出线程之间没有出现干扰的情况,变量都是从1递增到3,如果不使用ThreadLocal,那么三个线程同时访问一个变量就会出现并发的问题。
Thread-0:1
Thread-0:2
Thread-0:3
Thread-1:1
Thread-1:2
Thread-1:3
Thread-2:1
Thread-2:2
Thread-2:3
3.实际案例
在工作中也接触到了ThreadLocal,使用场景有在一次请求中保存用户信息;或者需要将日志记录到数据库中,每个请求的日志信息都不一样。
将用户信息保存到ThreadLocal中
在一次http请求中,会经过mvc三层,在这个过程中可能会在多个方法中需要获取用户信息,如果每次都读取数据库,会造成不必要的资源浪费。使用ThreadLocal能够达到一次读取,多次使用的效果。
//在请求中,验证用户信息无误后将用户信息存在ThreadLocal中,在这次请求
//直到返回结果的过程中,都可以通过getLoginUser()获取用户的信息。
public abstract class AbstractBaseController {
private static final ThreadLocal<LoginUserEntity> loginUserContainer = new ThreadLocal<>();
/**
* 初始化request和response对象
*/
@ModelAttribute
private final void initRequestAndResponse() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Objects.nonNull(authentication)) {
Object principal = authentication.getPrincipal();
if (principal instanceof LoginUserEntity) {
loginUserContainer.set((LoginUserEntity)principal);
}
}
}
/**
* 获取当前线程的登录用户user对象
*
* @return LoginUserEntity
*/
protected final LoginUserEntity getLoginUser() {
return loginUserContainer.get();
}
}
总结:ThreadLocal相比其他多线程解决方案来说,比较适合于变量隔离的场景,如果是要对一个值进行累加或累减再存回数据库,如秒杀的库存等,就不太适合使用ThreadLocal,那种业务场景还是适合使用同步来解决。