java之ThreadLocal原理及使用案例

 

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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的ThreadLocal类提供了一种线程本地变量的机制,可以在多线程环境下保证每个线程都有自己的变量副本,从而避免了线程安全问题。 ThreadLocal使用场景包括: 1. 数据库连接管理:在多线程环境下,每个线程需要独立的数据库连接。可以使用ThreadLocal来保存每个线程的数据库连接,从而避免线程之间的数据库连接混乱。 2. 日期格式化:在多线程环境下,使用SimpleDateFormat进行日期格式化可能存在线程安全问题。可以使用ThreadLocal来保存每个线程的SimpleDateFormat对象,从而避免线程之间的SimpleDateFormat对象共享。 3. 用户身份管理:在Web应用程序中,每个用户需要有自己的身份信息。可以使用ThreadLocal来保存每个线程对应的用户身份信息,从而避免线程之间的用户身份信息混淆。 下面是一个ThreadLocal案例: ```java public class UserContext { private static final ThreadLocal<User> currentUser = new ThreadLocal<>(); public static void setCurrentUser(User user) { currentUser.set(user); } public static User getCurrentUser() { return currentUser.get(); } public static void clear() { currentUser.remove(); } } ``` 在上面的示例中,UserContext类使用ThreadLocal来保存每个线程对应的用户信息。在Web应用程序中,可以在用户登录时调用UserContext.setCurrentUser()方法来保存用户信息,在用户退出时调用UserContext.clear()方法来清除用户信息。在其他地方需要获取当前用户信息时,可以调用UserContext.getCurrentUser()方法来获取当前线程对应的用户信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值