ThreadLocal是java.lang包下的类,用于保存线程级别的参数,在多并发编程中,为每一个线程创建一个单独的副本,实现彼此隔离。
理解一个类的最好方式就是学习其源码
我们常用的ThreadLocal,主要用其get和set方法,可以通过这两个方法的源码学习,掌握其基本原理。
1、set()方法的源码
方法很简单:1)获取当前线程;2)获取当前线程的map;3)map不为空就put,为空就创建一个map再put
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
每个线程都维护了一个ThreadLocalMap的成员变量,而这个ThreadLocalMap,其实就是自己实现的一个HashMap<ThreadLocal,Object>结构,如示例:
ThreadLocal<String> threadLocal = new ThreadLocal<String>();
线程1中:threadLocal.set("hello");
线程2中:threadLocal.set("world");
虽然有两个线程都调用了同一个threadLocal对象的set方法,但其实都保存在各自线程的map中,也就是等同于:
线程1中:map1.put(threadLocal,"hello");
线程2中:map2.put(threadLocal,"world");
它们是两个不同的map,只是用了同一个key而已
2、get()方法的源码
也很简单:1)获取当前线程;2)获取当前线程的map;3)map存在且值不为null就返回值,否则就调用初始化方法生成一个值并放入到map中,然后返回这个值,这个初始化方法内部主要是通过protected T initialValue(){return null;} 获取一个初始值,默认是null,方法本身是protected,说明了可以通过继承后重写的方式,自定义初始值;
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();
}
明白了上面的set示例,那么get也就毫无障碍了,接上面的实例:
线程1中:threadLocal.get() 等同于 map1.get(threadLocal),可以获得值 hello;
线程2中:threadLocal.get() 等同于map2.get(threadLocal),可以获得值world;
3、应用案例
Java中日期格式化类SimpleDateFormat是线程不安全的,所以如果我们工具类中按以下代码写,则是错误的写法:
public class DateUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static String formatDate(Date date) {
return sdf.format(date);
}
}
为了解决线程安全问题,我们可能会在方法中去new对象:
public class DateUtil {
public static String formatDate(Date date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);
}
}
或者将方法加锁:
public class DateUtil {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
public static synchronized String formatDate(Date date) {
return sdf.format(date);
}
}
这么写当然正确,不过性能上却不是最优的,根据我们说的ThreadLocal的特性,我们可以把代码改造为:
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat>();
public static String formatDate(Date date) {
if(sdf.get() == null) {
sdf.set(new SimpleDateFormat("yyyy-MM-dd"));
}
return sdf.get().format(date);
}
}
这样,同一个线程的多次调用,只会最多创建一个sdf实例,不过,这种使用方式并不太好,还记得上面说get时有个initialValue()方法吗,我们完全可以将这个方法利用上,写一个匿名子类,改造后代码如下:
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat>() {
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public static String formatDate(Date date) {
return sdf.get().format(date);
}
}