zoukankan      html  css  js  c++  java
  • 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());
        }
    }
    

      

  • 相关阅读:
    HDU 2844 Coins(多重背包)
    HDU 4540 威威猫系列故事——打地鼠(DP)
    Codeforces Round #236 (Div. 2)
    FZU 2140 Forever 0.5
    HDU 1171 Big Event in HDU(DP)
    HDU 1160 FatMouse's Speed(DP)
    ZOJ 3490 String Successor
    ZOJ 3609 Modular Inverse
    ZOJ 3603 Draw Something Cheat
    ZOJ 3705 Applications
  • 原文地址:https://www.cnblogs.com/cnxieyang/p/12745163.html
Copyright © 2011-2022 走看看