ThreadLocal使用场景

目录

1、应用场景

2、场景1创建对象副本-具体代码demo体现

第一版本-常规版本

第二版本-改进版

第三版本-引进ThreadLocal

第四版本:--优化代码

3、场景2-全局变量场景

4、源码浅谈

4.1 ThreadLocal、ThreadLocalMap、Thread关系

5、总结


1、应用场景

  1. 保存每个线程独享的对象、为每个线程创建一个副本、每个副本只为当前的线程服务
  2. 保存每个线程中需要独立保存的信息、针对每个线程类似于全局变量

2、场景1创建对象副本-具体代码demo体现

 我们以一个比较常见的例子,SimpleDateFormat 

第一版本-常规版本

  • 好处:编码简单
  • 劣势:内存中需要创建多余的对象

public class ThreadLocalDemo01 {

    private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");

    /**
     * 当前不存存在线程抢夺的情况,因为创建了1000个线程
     *
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int j = i;
            new Thread(() -> {
                String date = new ThreadLocalDemo01().date(j);
                System.out.println(j + "===>" + date);
            }).start();
        }
    }


    public String date(int i) {
        Date date = new Date(1000 * i);
        return simpleDateFormat.format(date);
    }


}

第二版本-改进版

  • 好处: 只创建了一个对象
  • 劣势: 多线程需要等待处理

public class ThreadLocalDemo02 {

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
    private static ExecutorService executorService = Executors.newFixedThreadPool(20);

    /**
     * 线程处理不安全,会出现资源抢夺情况
     * static 修饰的资源在常量池中。共用同一个
     * *
     *
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int j = i;
            executorService.submit(() -> {
                String date = new ThreadLocalDemo02().date(j);
                System.out.println(j + "===>" + date);
            });
        }
        executorService.shutdown();
    }


    public synchronized String date(int i) {
        Date date = new Date(1000 * i);
      // 方式1 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss"); // 每次调用都会创建一个对象、创建了1000个对象调用
        //方式2 添加Synchronize 关键字、但是有点得不偿失、其他线程都得等待
        synchronized (ThreadLocalDemo02.class){
            return simpleDateFormat.format(date);
        }
        /*** 共用同一个日期格式对象 ,出现线程不安全问题
         * return simpleDateFormat.format(date);
         */
    }


}

第三版本-引进ThreadLocal

  • 好处:会根据线程数来创建对应的对象,节省内存
  • 劣势:代码还可以优化一下

public class ThreadLocalDemo03 {

    private static ExecutorService executorService = Executors.newFixedThreadPool(10);

    static  Map map = new ConcurrentHashMap();

    /**
     * *
     *
     * @param args
     */
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int j = i;
            executorService.submit(() -> {
                String date = new ThreadLocalDemo03().date(j);
                System.out.println(j + "===>" + date);
            });
        }
        executorService.shutdown();
        TheadLocalHolder.clear();
    }

    public String date(int i) {
        Date date = new Date(1000 * i);
        SimpleDateFormat simpleDateFormat = TheadLocalHolder.dateFormatThreadLocal.get();
        // 验证是否同一个对象(开多少个线程就会有多少个对象)
        if (map.containsKey(simpleDateFormat)) {
            System.out.println("命中一次");
        } else {
            map.put(simpleDateFormat, 1);
            System.out.println("创建dateFormat对象");
        }
        // System.out.println(System.identityHashCode(simpleDateFormat));
        return simpleDateFormat.format(date);
    }
}

class TheadLocalHolder {
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss"));

    public static void clear() {
        dateFormatThreadLocal.remove();
    }

}

第四版本:--优化代码

  • 改进第三版本的复杂

public class ThreadLocalDemo04 {

    /**
     * 生产中禁用这种创建多线程的方式,应该采用线程池构建函数的方式创建
     */
    static ExecutorService executorService = Executors.newFixedThreadPool(7);
    static Map map = new ConcurrentHashMap();


    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int millSecond = i;
            executorService.submit(() -> {
                SimpleDateFormat simpleDateFormat =  ThreadLocal.withInitial(() -> new SimpleDateFormat("mm:ss")).get();
                Date date = new Date(millSecond * 1000);
                if (map.containsKey(simpleDateFormat)) {
                    System.out.println("命中一次");
                } else {
                    map.put(simpleDateFormat, 1);
                    System.out.println("创建dateFormat对象");
                }
                String date2 =  simpleDateFormat.format(date);
               // String date = new ThreadLocalDemo04().date(millSecond);
                System.out.println(millSecond + "==" + date2);
            });
        }
        executorService.shutdown();
    }
}

 

3、场景2-全局变量场景

我们在前面过滤器中可以放入对应的参数,在后面的需要用到时就不用每个参数透传了

比如: Person对象,只是部分处理器需要,还是需要每个方法都需要透传 ===>  我们就可以引入 TheadLocal来进行存


public class ThreadLocalContext {

    public static void main(String[] args) {
        Filter filter = new Filter();
        filter.process();
        // 需要调用 remove方法,防止内存泄漏
        UserHoldContext.userContext.remove();
        UserHoldContext.personContext.remove();
    }
}
class Filter{
    public void process(){
        User user = new User();
        user.setName("this is yx do thing ,");
        UserHoldContext.userContext.set(user);
        System.out.println("process");
        // 可以查看 ThreadLocal Thread ThreadLocalMap 对象之间的关系
        Person person = new Person(1);
        UserHoldContext.personContext.set(person);
        Service1 service1 = new Service1();
        service1.test1();
    }
}
class Service1 {
    public void test1() {
        User user = UserHoldContext.userContext.get();
        System.out.println("test1"+user.getName());
        Service2 service2 = new Service2();
        service2.test2();
    }
}

class Service2 {
    public void test2() {
        Service3 service3 = new Service3();
        User user = UserHoldContext.userContext.get();
        System.out.println("test2"+user.getName());
        service3.test3();
    }
}

class Service3 {
    public void test3() {
        User user = UserHoldContext.userContext.get();
        System.out.println(" test3"+user.getName());
        System.out.println("test3");
    }
}

class UserHoldContext {
    public  static ThreadLocal<User> userContext = ThreadLocal.withInitial(() -> {
        System.out.println("创建user对象");
        return new User();
    });
    public static ThreadLocal<Person> personContext = new ThreadLocal<Person>();
}
class Person{
    private int age;

    public Person() {
    }

    public Person(int age) {
        this.age = age;
    }
}

class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User() {
    }
}

4、源码浅谈

4.1 ThreadLocal、ThreadLocalMap、Thread关系

ThreadLocal对应关系

5、总结

demo 代码

# ThreadLocal的应用场景-有两个
- 场景1:保存每个线程独享的对象,为每个线程创建一个副本,这样每个线程可以修改自己所拥有的副本了
- 场景2:每个线程内的需要独立保存信息,以便其他方法可以方便的获取

场景1:
 典型的用法就是 SimpleDateFormat 对象
 在多线程中,没有必要每次都创建一个新的SimpleDateFormat,只需要为每个线程分配一个即可。可以引入threadLocal创建副本对象
 `com.yx.test.threadlocal.part1.ThreadLocalDemo04 ` 可以查看实现
场景2:
 保存每个线程分配的对象、类似于全局对象一样。比如我们在拦截器里面获取的userId,userType 
在后面的方法中也是需要调用,一般的处理方式,是直接透传、封装成一个map参数传递
其实这时候我们可以采用threadLocal进行透传,demo 如下
com.yx.test.threadlocal.part2.ThreadLocalContext

# Thread、ThreadLocalMap、ThreadLocal
每个Thread中都有一个ThreadLocalMap对象,一个ThreadLocalMap对象中有多个ThreadLocal,
key: ThreadLocal value 为存储的对象, 比如 User、Option

切记:切记: 在使用完成之后,需要从内存中移除,防止内存泄漏

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值