ThreadLocal的基本使用,以及它是如何保证线程安全
1.为什么会产生线程安全问题?
在多线程的环境当中,线程之间的变量它一般都是共享的。在堆中会维护共享的数据存储区域,这个区域数据为所有多个线程共享,能够访问并修改变量,所以多线程环境下,可能会存在线程安全问题。
2.使用ThreadLoacl实现线程隔离
ThreadLoacl会为当前线程维护一个变量副本,该变量副本只能被当前线程使用,其它线程无法访问该副本,因此能够解决线程安全问题。
ThreadLocal提供了三个常用方法,一个是set();添加变量;get()获取变量,以及remove()移除变量。
public class HostHolder {
// 创建ThreadLocal,存放user对象
private ThreadLocal<User> users = new ThreadLocal<User>();
public void setUser(User user) {
// 调用set传入user对象
users.set(user);
}
public User getUser(){
// 调用get()获取user对象
return users.get();
}
public void clearUser() {
// 移除user对象
users.remove();
}
}
上面的user对象,是我在项目中需要保护的资源,user对象存放的是用户的信息,为用户独享,所以需要保证线程安全。
3.ThreadLocal如何保证线程安全?
主要是用到了ThreadLoaclMap对象,这个对象是当前线程维护的变量副本,为当前线程共享。它是一个哈希表,维护了当前线程所有的ThreadLocal对象;hash表的key就是ThreadLocal对象的哈希值,value就是要存储的变量。具体如下图:
上述代码中,ThreadLocal<User> users = new ThreadLocal<User>();
hash表的key就是 users对象的hash值,而value就是 users.set(user);方法传入的user对象。
set()方法源码如下:记住一个线程内ThreadLocalMap对象只有一个,初始为null,第一次往里添加元素时才初始化。
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 因为ThreadLocalMap的对象初始为空,第一次添加元素时才会初始化
// 所以需要判空
if (map != null)
// 如果ThreadLocalMap对象已经创建,就直接将对象存入hash表
map.set(this, value);
else
// 如果还为创建,则初始化ThreadLocalMap对象之后,将需要存储的对象存入hash表
createMap(t, value);
}
get()方法:
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果ThreadLocalMap对象存在,就去获取它存储的对象
if (map != null) {
// 获取调用者也就users的entry,entry是key-value形式
ThreadLocalMap.Entry e = map.getEntry(this);
// entry不为null,就获取它的value并返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果不存在就调用初始化方法
return setInitialValue();
}
setInitialValue()逻辑和set()方法类似。了解一下,实际上,setInitialValue()
方法是在 get()
方法中被调用,用于初始化变量的初始值并存储到 ThreadLocalMap 中。调用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;
}
// 创建ThreadLocal,存放user对象
private ThreadLocal<User> users = new ThreadLocal<User>(){
// 需要重写initialValue去返回user对象
@Override
protected User initialValue() {
return getUser();
}
};
public User getUser(User user) {
return user;
}
原理总结:ThreadLocal底层实际上就是为每个线程维护了ThreadLocalMap对象,ThreadLocalMap本质是一个哈希表,它的主要作用是为每个线程提供一个独立的变量副本,这样不同线程之间的变量互不干扰,从而实现线程隔离实现线程隔离,使用时从哈希表中去获取对应的变量。