ThreadLocal是线程封闭的重要措施,Spring中DateTimeContextHolder和RequestContextHolder也有用到。
ThreadLocal的使用场景
场景1:对象隔离–线程需要一个独享的对象(例如SimpleDateFormat)
线程独享一个对象工具类,例如Random、DateFormat,通常出于线程安全、提高效率两方面的考虑。
对于线程独享对象场景,主要是重写innitialValue()方法。
public class RrightWaySimpleDateFormater {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
/**
* 定义ThreadLocal变量--JDK8实现形式
*/
private static final ThreadLocal<SimpleDateFormat> dateThreadSafe = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit(() -> {
System.out.println(new RrightWaySimpleDateFormater().date(finalI));
});
}
threadPool.shutdown();
}
public String date(int seconds) {
return dateThreadSafe.get().format(new Date(1000 * seconds));
}
}
JDK7以及之前的实现形式
private static final ThreadLocal<SimpleDateFormat> dateThreadSafe2 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
需要注意的是,ThreadLocal通常不适合线程池。
阿里java手册:
【强制】SimpleDateFormat是线程不安全的类,一般不要定义为static变量。如果定义为static,必须加锁,或使用DateUtils工具类。亦推荐上述处理。
JDK8推荐使用DateTimeFormatter代替SimpleDateFormat,Instant代替Date,LocalDateTime代替Calendar。
场景2:对象传递
线程需要保存全局变量,可以让不同的方法直接使用,而不需要让数据作为参数层层传递。
ThreadLocal可以用于保存一些业务信息,例如用户权限信息、用户名、userId、手机号等等。这些业务信息在同一个线程中相同,不同线程中内容不同。强调的是同一个请求内(同一个线程内)不同方法间的共享。
Map也可以存储上述业务信息。多线程同时工作时,需要保证线程安全。例如,采用静态ConcurrentHashMap变量,将线程ID作为key,业务数据作为Value保存,可以做到线程间隔离,但仍有性能影响。
public class RightWayThreadLocalUse {
public static void main(String[] args) {
new ServiceImpl1().service(new UserInfo("lzp", "1234567890"));
}
}
/**
* 接收数据
*/
class ServiceImpl1 {
public void service(UserInfo userInfo) {
UserInfoHolder.holder.set(userInfo);
new ServiceImpl2().service();
}
}
/**
* 处理数据1
*/
class ServiceImpl2 {
public void service() {
System.out.println("客户名:" + UserInfoHolder.holder.get().cltNam);
new ServiceImpl3().service();
}
}
/**
* 处理数据2
*/
class ServiceImpl3 {
public void service() {
System.out.println("客户号:" + UserInfoHolder.holder.get().cltNbr);
// 此时使用完ThreadLocal,回收该ThreadLocal
UserInfoHolder.holder.remove();
}
}
class UserInfoHolder {
public static ThreadLocal<UserInfo> holder = new ThreadLocal<>();
}
class UserInfo {
String cltNam;
String cltNbr;
public UserInfo(String cltNam, String cltNbr) {
this.cltNam = cltNam;
this.cltNbr = cltNbr;
}
}
ThreadLocal的优势
(1)ThreadLocal变量线程私有,因此线程安全,不需要加锁,从而没有阻塞,提高了执行效率。
(2)高效利用内存,节省开销。对于使用线程池的场景,只需要每个woker线程拥有一个该对象的ThreadLocal实例即可。
(3)对于调用链场景,避免对象的反复传递,用于存储业务信息,实现代码解耦。