ThreadLocal使用场景

Posted learning_code_blog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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关系

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

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

以上是关于ThreadLocal使用场景的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出多线程编程实战ThreadLocal详解(介绍使用原理应用场景)

ThreadLocal的使用场景分析

ThreadLocal使用场景

ThreadLocal使用场景

ThreadLocal的使用场景及使用方式

ThreadLocal使用场景分析