ThreadLocal不会难到我们吧?

大家好,我是方圆
一定要学好多线程!

目录

    • 1. Thread、ThreadLocal和ThreadLocalMap三者的关系
    • 2. get方法
    • 3. set方法
      • 3.1 createMap方法
    • 4. remove方法
      • 4.1 重点说一说它的Value值内存泄漏问题
        • 4.1.1 先看看Entry中强引用和弱引用
        • 4.1.2 OOM问题我们需要注意的点
    • 5. ThreadLocal使用的两大场景
      • 5.1 每个线程需要一个独享的对象
      • 5.2 每个线程需要保存全局变量


1. Thread、ThreadLocal和ThreadLocalMap三者的关系

每个Thread中都有一个ThreadLocalMap对象,而ThreadLocalMap中存储的是多个ThreadLocal对象
ThreadLocal不会难到我们吧?_第1张图片
其中ThreadLocalMap中的key为ThreadLocal对象,value为ThreadLocal中是我们要存储的值


2. get方法

ThreadLocal不会难到我们吧?_第2张图片


3. set方法

ThreadLocal不会难到我们吧?_第3张图片

3.1 createMap方法

ThreadLocal不会难到我们吧?_第4张图片
ThreadLocal不会难到我们吧?_第5张图片


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不会难到我们吧?_第6张图片
    在弱引用下,我们使用完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");
    }
}

加油儿!!!

你可能感兴趣的:(Java并发编程)