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);
	}
}