ThreadLocal的两种用法
典型场景1:每个线程需要一个独享的对象
每个Thread内有自己的实例副本,不共享。
用lambda表达式创建线程安全的DataFormat对象
/**
* 描述: 利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存
*/
public class ThreadLocalNormalUsage05 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
int finalI = i;
threadPool.submit(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalNormalUsage05().date(finalI);
System.out.println(date);
}
});
}
threadPool.shutdown();
}
public String date(int seconds) {
//参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
Date date = new Date(1000 * seconds);
// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
return dateFormat.format(date);
}
}
class ThreadSafeFormatter {
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
/**
* 第二种写法
* 用lambda表达式写
*/
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
典型场景2:每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦。
用ThreadLocal保存一些业务内容(用户权限信息、从用户系统获取到的用户名、userID等)
这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。
强调的是同一个请求内(同一个线程内)不同方法间的共享
不需要重写initialValue方法,但是必须手动调用set()方法。
ThreadLocal的好处
达到线程安全
不需要加锁,提高执行效率
更高效地利用内存、节省开销:相比于每个任务都新建一个SimpleDateFormate,显然用ThreadLocal可以节省内存和开销。
免去传参的繁琐:无论是场景一的工具类,还是场景二的用户名,都可以在、任何地方直接通过ThreadLocal拿到,再也不需要每次都传同样的参数。ThreadLocal使得代码耦合度更低,更优雅。
ThreadLocal原理
Thread类中有一个ThreadLocalMap成员变量,他可以保存多个ThreadLocal对象及对应的值。
ThreadLocal的重要方法介绍
重要方法的源码分析
ThreadLocal的空指针异常问题
public class ThreadLocalNPE {
ThreadLocal<Long> longThreadLocal = new ThreadLocal<Long>();
public void set() {
longThreadLocal.set(Thread.currentThread().getId());
}
public long get() {
return longThreadLocal.get();
}
public static void main(String[] args) {
ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE();
System.out.println(threadLocalNPE.get());
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadLocalNPE.set();
System.out.println(threadLocalNPE.get());
}
});
thread1.start();
}
}
public long get() {return longThreadLocal.get();}中的返回值类型必须是Long(包装类),不然会出现空指针异常。
原理设计到装箱和拆箱
ThreadLocal在Spring中应用