话不多说,直奔主题
源码分析
/**
*这个类可以为每个线程分配某个类独立对象(局部变量),达到线程安全通常
*是工具类,典型的SimpleDateFormat和Random,还有就是为每个线程内需要保 存全,比如在拦截器中配置登录用户信息,
*局变量可以让不同方法直接使用,避免参数传递的额麻烦
*
* 当某个线程消失后,分配给这个线程的对象副本会被GC
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {
//构造方法
/**
* 创建一个本地局部变量.
*/
public ThreadLocal() {
}
//重要方法
/**
*将当前线程的此线程局部变量设置为指定的值。 大多数子类将无需重
*写此方法,仅依靠initialValue()方法设置局部变量,也就是初始化
*
* @return 对象的副本
*/
public T get() {
//当前线程
Thread t = Thread.currentThread();
/**
*Thread类的属性,用键值对存放每个线程的threadLocals
* ThreadLocal.ThreadLocalMap threadLocals = null;
*/
//获取maps
ThreadLocalMap map = getMap(t);
if (map != null) {
//map的键就是本类对象也就是ThreadLocal对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
//如果值不为空直接返回返回
return result;
}
}
//初始化值并返回
return setInitialValue();
}
/**
* 返回此线程局部变量的当前线程的“初始值”。 该方法将在第一次使用get()
* 方法访问变量时被调用,除非线程先前调用了set(T)方法,在这种情况下,
* initialValue方法将不会被调用。 通常情况下,这种方法最多每个线程调
* 用一次,但它可能会再次调用,在remove()后或者其次是get()首次调用。
* 这个实现简单地返回null ; 如果希望线程局部变量具有除null之外的
* 初始值,则ThreadLocal必须被子类化,并且该方法被覆盖。 通常,将使用
* 匿名内部类。
*
* @return 这个线程本地的初始值
*/
protected T initialValue() {
return null;
}
/**
* 设置初始化值
*
* @return 初始值
*/
private T setInitialValue() {
//真正初始化方法,并返回值的副本
T value = initialValue();
Thread t = Thread.currentThread();
//线程存放ThreadLocal对象的map
ThreadLocalMap map = getMap(t);
if (map != null)
//将值设置进去,键是本类对象
map.set(this, value);
else
//初始化map,并传入一个键值对
createMap(t, value);
//返回值
return value;
}
/**
*创建线程局部变量。变量的初始值是通过调用Supplier的get方法来确定的。
*
* @param <S> the type of the thread local's value
*
* @return 一个新的线程本地变量
* @throws NullPointerException
* @since 1.8
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
/**
* 将当前线程的此线程局部变量的副本设置为指定的值。
* 大多数子类将无需重写此方法,仅依靠initialValue()方法设置线程本地值
* 的值。
*
*/
public void set(T value) {
Thread t = Thread.currentThread();
//线程存放ThreadLocal对象的map
ThreadLocalMap map = getMap(t);
if (map != null)
//将值设置进去,键是本类对象
map.set(this, value);
else
//初始化map,并传入一个键值对
createMap(t, value);
}
/**
* 移除局部变量的值,之后如果想获取这个值,将通过调用其initialValue()
* 方法重新初始化 ,除非其值在中间由当前线程set()过。 这可能导致当前线
* 程中的initialValue方法的多次调
* 用。
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
案例分析1
/**
* 描述: 1000个打印日期的任务,用线程池来执行
*/
public class ThreadLocalNormalUsage03 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
static SimpleDateFormat dateFormat = 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(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalNormalUsage03().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);
return dateFormat.format(date);
}
}
。。。。
pool-1-thread-51970-01-01 08:00:34
pool-1-thread-41970-01-01 08:00:24
pool-1-thread-61970-01-01 08:00:24
pool-1-thread-91970-01-01 08:00:22
。。。。
从结果看发生线程不安全,存在相同的时间
解决办法1加锁来解决线程安全问题
/**
* 描述: 加锁来解决线程安全问题
*/
public class ThreadLocalNormalUsage04 {
public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
static SimpleDateFormat dateFormat = 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(new Runnable() {
@Override
public void run() {
String date = new ThreadLocalNormalUsage04().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);
String s = null;
synchronized (ThreadLocalNormalUsage04.class) {
s = dateFormat.format(date);
}
return s;
}
}
枷锁虽然能保证线程安全,但是存在阻塞,效率低下
解决办法2ThreadLocal为每一个线程分配不同的对象保证线程安全
/**
* 描述: 利用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");
}
};
// public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
// .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 =ThreadLocal.withInitial(new Supplier<SimpleDateFormat>() {
@Override
public SimpleDateFormat get() {
// TODO Auto-generated method stub
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
});
}
方法2为每个线程绑定一个SimpleDateFormat对象,也就是线程池的线程,这里为10,而且不存在阻塞,效率高
案例分析2
/**
* 描述: 演示ThreadLocal用法2:避免传递参数的麻烦
*/
public class ThreadLocalNormalUsage06 {
public static void main(String[] args) {
new Service1().process("");
}
}
class Service1 {
public void process(String name) {
User user = new User("Hello World");
UserContextHolder.holder.set(user);
new Service2().process();
}
}
class Service2 {
public void process() {
User user = UserContextHolder.holder.get();
ThreadSafeFormatter.dateFormatThreadLocal.get();
System.out.println("Service2拿到用户名:" + user.name);
new Service3().process();
}
}
class Service3 {
public void process() {
User user = UserContextHolder.holder.get();
System.out.println("Service3拿到用户名:" + user.name);
UserContextHolder.holder.remove();
}
}
class UserContextHolder {
public static ThreadLocal<User> holder = new ThreadLocal<>();
}
class User {
String name;
public User(String name) {
this.name = name;
}
}
执行结果:
Service2拿到用户名:Hello World
Service3拿到用户名:Hello World
我们发现通过ThreadLocal在 Service1创建的对象 Service2和 Service3很容易通过ThreadLocal拿到,避免参数的传递
原理分析
回到get方法
/**
*将当前线程的此线程局部变量的副本设置为指定的值。 大多数子类将无需重
*写此方法,仅依靠initialValue()方法设置局部变量,也就是初始化
*
* @return 对象的副本
*/
public T get() {
//当前线程
Thread t = Thread.currentThread();
/**
*Thread类的属性,用键值对存放每个线程的threadLocals
* ThreadLocal.ThreadLocalMap threadLocals = null;
*/
//获取maps
ThreadLocalMap map = getMap(t);
if (map != null) {
//map的键就是本类对象也就是ThreadLocal对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
//如果值不为空直接返回返回
return result;
}
}
//初始化值并返回
return setInitialValue();
}
initialValue()初始化之后,每次通过get方法获取值,都是从map里获取,而map是由当前线程自己管理,通常initialValue()方法只会执行一次,除非在get之前set过或者remove过,所以每次都是获取同一个值
最后为了避免内存泄漏在使用了Threadlocal完,就要remove。把key删除也就是threadlocal