• ThreadLocal从入门到精通--彻底掌握


    本文带领你从各个方面了解并掌握ThreadLocal,进而彻底精通。

    使用场景

      1.每个线程需要一个独享的对象(不安全的工具类)

      2.每个线程需要保存全局变量(可让不同的方法调用)

      以下实例代码对共享的操作添加了类锁,会有性能问题,见下图

      

    package threadPool;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 测试不使用ThreadLocal时如何对线程的共享变量加锁
     */
    public class NotUseThreadLocalTest01 {
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        public static void main(String[] args) {
            //创建线程池去执行1000个任务
            ExecutorService executorService= Executors.newFixedThreadPool(10);
            NotUseThreadLocalTest01 threadLocalTest01=new NotUseThreadLocalTest01();
            for (int i=0;i<1000;i++){
                int finalI = i;
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        //获取当前时间戳
                        System.out.println(threadLocalTest01.getDateStrFromSeconds(finalI));
                    }
                });
            }
            executorService.shutdown();
    
    
        }
    
        public String getDateStrFromSeconds(int seconds){
            Date date=new Date(seconds*1000);
            String dateStr=null;
            //加类锁
            synchronized (NotUseThreadLocalTest01.class){
                dateStr=simpleDateFormat.format(date);
            }
            return dateStr;
        }
    }
    

      以下实例代码我们使用threadlocal去生成dateformat,见下文。

       

    package threadPool;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 测试ThreadLocal时如何对线程的共享变量加锁
     */
    public class UseThreadLocalTest02 {
        public static void main(String[] args) {
            //创建线程池去执行1000个任务
            ExecutorService executorService= Executors.newFixedThreadPool(10);
            UseThreadLocalTest02 threadLocalTest01=new UseThreadLocalTest02();
            for (int i=0;i<1000;i++){
                int finalI = i;
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        //获取当前时间戳
                        System.out.println(threadLocalTest01.getDateStrFromSeconds(finalI));
                    }
                });
            }
            executorService.shutdown();
    
    
        }
    
        public String getDateStrFromSeconds(int seconds){
            Date date=new Date(seconds*1000);
            return DateFormatter.simpleDateFormatThreadLocal.get().format(date);
        }
    }
    
    class DateFormatter{
        public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal=new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            }
        };
        //也可以使用lamda初始化
        public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocalWithLamda=ThreadLocal.withInitial(()->
            new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")
        );
    }
    

      以下实现,我们对线程中不同的方法基于threadlocal传递参数,见下图

      

    package threadPool;
    
    /**
     * 实现主线程,基于threadlocal传递参数
     */
    public class UserThreadLocal03 {
    
        public static void main(String[] args) {
            try{
                UserThreadLocal03 userThreadLocal03=new UserThreadLocal03();
                userThreadLocal03.serviceOne();
            }finally {
                 ThreadLocalParams.userThreadLocal.remove();
            }
    
        }
    
        public void  serviceOne(){
            ThreadLocalParams.userThreadLocal.set(new User("123"));
            serviceTwo();
        }
        public void serviceTwo(){
            System.out.println("userId:"+ThreadLocalParams.userThreadLocal.get().getId());
            serviceThree();
        }
        public void serviceThree(){
            System.out.println("userId:"+ThreadLocalParams.userThreadLocal.get().getId());
        }
    
    }
    
    class User
    {
        private String id;
    
        public User(String id) {
            this.id = id;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    }
    
    class ThreadLocalParams{
        public static ThreadLocal<User> userThreadLocal=new ThreadLocal<User>();
    }
    

     ThreadLocal方法介绍

      1.initialValue:初始化

      2.get 获取当前线程threadlocal对应的值

      3.set 设置当前线程threadLocal的值

      4.remove移除

     ThreadLocal部分源码分析

      主要分析initialValue、get、set等

      get方法如下

      

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

      get方法首先会从当前threadlocalMap中获取,如果有了,直接返回,若没有的时候,则会调用initialValue方法后返回,则说明initialValue这个方法是延迟加载的,只有在用的时候才会加载,接下面我们查看下set方法的部分实现。

      set方法的实现如下图所示:

      

      public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

      从上面代码中我们发现set方法最重要的调用方法为map.set,将线程变量的值进行写入,接下面我们查看下initialValue方法,

      initialValue方法部分实现如下图所示:

      

      private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
    

      我们发现initialValue方法最终调用的也是map.set方法,进而可知,initialValue和set的实现有异曲同工之妙。

    ThreadLocal内存泄漏问题

      我们查看remove代码可以看到下面这样的实现,会执行clear操作,所有在线程池中传递变量后,不再使用时,需要显示执行remove等类似方法进行清除。

      

     private void remove(ThreadLocal<?> key) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    if (e.get() == key) {
                        e.clear();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            }
    
    .............
    
     public void clear() {
            this.referent = null;
        }
    
    
    .............
    
    private T referent;         /* Treated specially by GC */
    

      ThreadLocal优点

      1.解决线程安全问题

      2.减少内存的使用

      3.避免或者减少线程方法中的方法传递

      ThreadLocal空指针异常

        详细介绍空指针异常。threadLocal本身没有空指针异常, 多数情况是类型进行自动装箱时导致的,可以看下实例代码,如下所示:

        

    package threadPool;
    
    public class ThreadLocalNullPointExceptionTest {
        private ThreadLocal<Long> threadLocal= new ThreadLocal<Long>();
        public void set(){
            threadLocal.set(Thread.currentThread().getId());
        }
    
        /**
         * 空的null转成long的时候会出现空指针异常
         * Exception in thread "main" java.lang.NullPointerException
         * 	at threadPool.ThreadLocalNullPointExceptionTest.get(ThreadLocalNullPointExceptionTest.java:9)
         * 	at threadPool.ThreadLocalNullPointExceptionTest.main(ThreadLocalNullPointExceptionTest.java:14)
         * @return
         */
        public long get(){
            return threadLocal.get();
        }
    
        public static void main(String[] args) {
            ThreadLocalNullPointExceptionTest threadLocalNullPointExceptionTest=new ThreadLocalNullPointExceptionTest();
            System.out.println(threadLocalNullPointExceptionTest.get());
        }
    }
    

      

  • 相关阅读:
    netstat -ano 查看机器端口占用情况
    配置中心Nacos 20210908
    ABAP-查询系统表记录
    ABAP-自定义表维护程序
    linux 常用操作
    自动化回归测试实战
    LeetCode大部分是medium难度不怎么按顺序题解(下)
    bitmapCache.getDataURL is not a function BUG修复
    createjs 刮刮卡,刮开百分比。 含源文件
    【nim语言】nim语言 1.4.8编译时SSL报错“No SSL/TLS CA certificates found” 解决方法。
  • 原文地址:https://www.cnblogs.com/cnxieyang/p/12745163.html
Copyright © 2020-2023  润新知