大家好,我是
方圆
一定要学好多线程!
目录
1. Thread、ThreadLocal和ThreadLocalMap三者的关系
每个Thread中都有一个ThreadLocalMap对象,而ThreadLocalMap中存储的是多个ThreadLocal对象
其中ThreadLocalMap中的key
为ThreadLocal对象,value
为ThreadLocal中是我们要存储的值
2. get方法
3. set方法
3.1 createMap方法
4. remove方法
这个方法比较简单,大家过一眼就能看明白
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
4.1 重点说一说它的Value值内存泄漏问题
4.1.1 先看看Entry中强引用和弱引用
Entry
为ThreadLocalMap中的一个静态内部类,我们看看源码
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
-
我们可以明确的发现value值用的是
强引用
,而对于key值则调用了父类的方法,我们点进去看看
在弱引用下,我们使用完ThreadLocal对象,它就会被回收,也就是说key值会为null;
但是value就不一样了,它是强引用,会与线程的声明周期一样,线程不停止就会一直存在,这样就可能造成OOM问题 -
引用链
4.1.2 OOM问题我们需要注意的点
ThreadLocal在set方法
和remove方法
中其实都会扫描key为null的Entry,并把对应的value也设置为null,这样就能避免了OOM问题,从而对value对象进行回收。
有了以上措施,但是还是需要我们进行方法调用才能进行清除,所以,我们自己必须要注意到这一点,而且阿里规约中也有说明:使用完ThreadLocal对象之后,应该调用remove方法,删除Entry对象,避免内存泄漏
5. ThreadLocal使用的两大场景
5.1 每个线程需要一个独享的对象
//第一种情况,每次执行date方法都会新建一个dateFormat对象,浪费空间
//第二种情况,我们把方法中的format提取出来,作为共享资源使用,但是会出现重复,造成线程不安全
//第三种情况,使用ThreadLocal,使得当前线程中都有一个私有对象使用,调用get方法获取,witInitial来添加,supplier函数式接口
//TODO 这种情况调用的是withInitial方法,因为对象的生成是由ThreadLocal控制的,而之后的set方法,对象的生成不由ThreadLocal控制
public class ThreadLocalDemo1 {
private static ExecutorService pool = Executors.newFixedThreadPool(8);
private 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 temp = i;
pool.execute(() -> {
System.out.println(date(temp));
});
}
TimeUnit.SECONDS.sleep(1);
pool.shutdown();
}
private static String date(int seconds){
Date date = new Date(seconds * 1000);
// SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
SimpleDateFormat simpleDateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")).get();
return simpleDateFormat.format(date);
}
}
class ThreadLocalSafeSimpleDateFormat {
public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
}
5.2 每个线程需要保存全局变量
//TODO 这个使用场景区别于上一个,首先方法用的是set,因为存储对象不由ThreadLocal控制
//其次呢,这个场景重要是应用在多个服务要调用一个参数时,避免重复传递,直接由ThreadLocal获取
//它的好处:线程安全;避免传参的麻烦
public class ThreadLocalDemo2 {
public static void main(String[] args) {
new Service1().doService();
}
}
class User {
private String name;
public User(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
class UserContextHolder {
public static ThreadLocal<User> userHolder = new ThreadLocal<>();
}
class Service1 {
public void doService() {
User user = new User("小腚腚");
UserContextHolder.userHolder.set(user);
System.out.println("小腚腚在服务1中加入了");
new Service2().doService();
}
}
class Service2 {
public void doService() {
User user = UserContextHolder.userHolder.get();
System.out.println(user.toString() + "在服务2中努力学习ThreadLocal");
new Service3().doService();
}
}
class Service3 {
public void doService() {
User user = UserContextHolder.userHolder.get();
System.out.println(user.toString() + "在服务3中努力刷LeetCode");
}
}
加油儿!!!