zoukankan      html  css  js  c++  java
  • ThreadLocal详解

    1.大纲

      使用场景

      带来的好处

      主要方法的介绍

    2.两大使用场景

      每个线程需要一个独享的对象

      每个线程内需要保存全局变量,可以让不同的方法直接使用,避免参数传递的麻烦

    一:场景一

    1.说明

      每个线程都需要一个独享的对象

    2.普通的线程运行SimpleDateFormat

    package com.jun.juc.threadlocal;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     *
     */
    public class ThreadLocalDateFormat {
        public String date(int seconds){
            Date date = new Date(seconds * 1000);
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return dateFormat.format(date);
        }
    
        public static void main(String[] args) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String dateStr = new ThreadLocalDateFormat().date(10);
                    System.out.println("dateStr1:"+dateStr);
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    String dateStr = new ThreadLocalDateFormat().date(12);
                    System.out.println("dateStr2:"+dateStr);
                }
            }).start();
        }
    }
    

      效果:

    Disconnected from the target VM, address: '127.0.0.1:49859', transport: 'socket'
    dateStr1:1970-01-01 08:00:10
    dateStr2:1970-01-01 08:00:12
    

      

    3.使用线程池

      当上面的两个线程不够用的时候,我们开始考虑线程池。

    package com.jun.juc.threadlocal;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 开始使用线程池处理
     */
    public class ThreadLocalDateFormatWithThreadPool {
        private static ExecutorService executorService = Executors.newFixedThreadPool(10);
        public String date(int seconds){
            Date date = new Date(seconds * 1000);
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            return simpleDateFormat.format(date);
        }
    
        public static void main(String[] args) {
            for (int i=0;i<10;i++){
                int finalI= i;
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        String dateStr = new ThreadLocalDateFormatWithThreadPool().date(finalI);
                        System.out.println("dateStr="+dateStr);
                    }
                });
            }
            executorService.shutdown();
        }
    }
    

      效果:

    Connected to the target VM, address: '127.0.0.1:50826', transport: 'socket'
    dateStr=1970-01-01 08:00:04
    dateStr=1970-01-01 08:00:00
    dateStr=1970-01-01 08:00:08
    dateStr=1970-01-01 08:00:09
    dateStr=1970-01-01 08:00:01
    dateStr=1970-01-01 08:00:03
    dateStr=1970-01-01 08:00:02
    dateStr=1970-01-01 08:00:06
    dateStr=1970-01-01 08:00:05
    dateStr=1970-01-01 08:00:07
    Disconnected from the target VM, address: '127.0.0.1:50826', transport: 'socket'
    

      

    4.问题

      上面需要新建很多个SimpleDateFormat。

      然后,我们使用同一个对象。

    package com.jun.juc.threadlocal;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 开始使用线程池处理
     */
    public class ThreadLocalDateFormatWithThreadPoolAndStatic {
        private static ExecutorService executorService = Executors.newFixedThreadPool(3);
        //提取出来
        static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        public String date(int seconds){
            Date date = new Date(seconds * 1000);
            return simpleDateFormat.format(date);
        }
    
        public static void main(String[] args) {
            for (int i=0;i<20;i++){
                int finalI= i;
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        String dateStr = new ThreadLocalDateFormatWithThreadPoolAndStatic().date(finalI);
                        System.out.println("dateStr="+dateStr);
                    }
                });
            }
            executorService.shutdown();
        }
    }
    

      效果:

    D:jdk1.8.0_144injava -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:51581,suspend=y,server=n -D
    Connected to the target VM, address: '127.0.0.1:51581', transport: 'socket'
    dateStr=1970-01-01 08:00:00
    dateStr=1970-01-01 08:00:00
    dateStr=1970-01-01 08:00:00
    dateStr=1970-01-01 08:00:04
    dateStr=1970-01-01 08:00:04
    dateStr=1970-01-01 08:00:06
    dateStr=1970-01-01 08:00:07
    dateStr=1970-01-01 08:00:08
    dateStr=1970-01-01 08:00:08
    dateStr=1970-01-01 08:00:10
    dateStr=1970-01-01 08:00:10
    dateStr=1970-01-01 08:00:13
    dateStr=1970-01-01 08:00:13
    dateStr=1970-01-01 08:00:13
    dateStr=1970-01-01 08:00:15
    dateStr=1970-01-01 08:00:15
    dateStr=1970-01-01 08:00:17
    dateStr=1970-01-01 08:00:18
    dateStr=1970-01-01 08:00:17
    dateStr=1970-01-01 08:00:19
    Disconnected from the target VM, address: '127.0.0.1:51581', transport: 'socket'
    
    Process finished with exit code 0
    

      原因:当线程一进来之后,中断,线程二过来,修改,然后中断,如果,线程一过来,则使用的是线程二修改过得值

    5.加锁处理这种问题

    package com.jun.juc.threadlocal;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 加锁处理线程问题
     */
    public class ThreadLocalDateFormatWithSynchronized {
        private static ExecutorService executorService = Executors.newFixedThreadPool(3);
        //提取出来
        static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
        public String date(int seconds) {
            Date date = new Date(seconds * 1000);
            String format = "";
            synchronized (ThreadLocalDateFormatWithSynchronized.class) {
                format = simpleDateFormat.format(date);
            }
            return format;
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 20; i++) {
                int finalI = i;
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        String dateStr = new ThreadLocalDateFormatWithSynchronized().date(finalI);
                        System.out.println("dateStr=" + dateStr);
                    }
                });
            }
            executorService.shutdown();
        }
    }
    

      

    6.使用ThrealLocal 

      加锁可以处理上面的问题,但是对性能有损耗

    package com.jun.juc.threadlocal;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 开始使用线程池处理,使用ThreadLocal
     */
    public class DateFormatWithThreadPoolLast {
        private static ExecutorService executorService = Executors.newFixedThreadPool(10);
        public String date(int seconds){
            Date date = new Date(seconds * 1000);
            SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
            return simpleDateFormat.format(date);
        }
    
        public static void main(String[] args) {
            for (int i=0;i<10;i++){
                int finalI= i;
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        String dateStr = new DateFormatWithThreadPoolLast().date(finalI);
                        System.out.println("dateStr="+dateStr);
                    }
                });
            }
            executorService.shutdown();
        }
    }
    
    /**
     * 产生安全的SimpleDateFormat
     */
    class ThreadSafeFormatter{
        public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>(){
            @Override
            protected SimpleDateFormat initialValue() {
                return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            }
        };
    }
    

      

    7.总结场景一

      有时候,我们不需要总是新建多个对象,就考虑提取出来,这个可能会出现线程安全问题。

      然后,第一个反应是加锁。但是加锁会存在性能问题,这个时候,可以考虑使用ThreadLocal,保证每个线程是安全的。

    二:场景二

    1.使用场景

      

      使用ThreadLocal保存一些业务内容

      这些内容在同一个线程内相同,但是不同的线程中使用的业务内容是不同的

      在线程的生命周期之内,都通过这个静态的ThreadLocal实例的get方法取得自己set过得对象,避免了将这个对象作为参数传递的麻烦,程序更加优雅

    2.方法

      强调的是同一个请求内不同方法间的共享

      不要重写initialValue()方法,但是必须手动调用set()方法

    3.实现

    package com.jun.juc.threadlocal.second;
    
    /**
     * 多个方法使用同一个参数
     */
    public class Transmit {
        public static void main(String[] args) {
            new Service1().process();
        }
    }
    
    
    class User{
        private String name;
        public User(String name){
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    }
    
    class UserThreadLocalHolder{
        public static ThreadLocal<User> holder = new ThreadLocal<>();
    }
    class Service1{
        public void process(){
            User user = new User("tom");
            UserThreadLocalHolder.holder.set(user);
            new Service2().process();
        }
    }
    
    class Service2{
        public void process(){
            User user = UserThreadLocalHolder.holder.get();
            System.out.println("user2:"+user.getName());
            // 修改
            user = new User("bob");
            UserThreadLocalHolder.holder.set(user);
            new Service3().process();
        }
    }
    
    class Service3{
        public void process(){
            User user = UserThreadLocalHolder.holder.get();
            System.out.println("user3:"+user.getName());
            UserThreadLocalHolder.holder.remove();
        }
    }
    

      

    三:方法说明

    1.initialValue

      在ThreadLocal第一次get的时候把对象给初始化出来,对象的初始化时机是可以让我们来控制的

    2.set  

      生成的时机不由我们随意的控制,只能使用set进行设置

    3.好处

      线程安全

      不需要加锁,提高了效率

      高效的利用内存,节省开销

      避免传参的繁琐,耦合低,代码优雅

    四:原理

    1.原理图

      

    2.ThreadLocalMap

      这行代码在Thread中:

    /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    

      然后,看设置。发现,设置进来的是this为key

        /**
         * Create the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param t the current thread
         * @param firstValue value for the initial entry of the map
         */
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

      

    3.程序里,哪里使用createMap

      初始化

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

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

      结论:

      刚好对应了上文说的两种场景。

    4.initialValue

    protected T initialValue() {
            return null;
        }
    

      结论:

      方法会返回当前线程对应的初始化值,所以要进行自己重写该方法

        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时,先判断是否有这个数据,没有,则走set初始化

      结论:

      本方法还是延迟加载,在第一次get访问变量时,才进行调用调用本方法。

      如果之前,有了set,才不会调用本方法

      结论2:

      只会触发一次setInitialValue

    5.get

      再讲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();
        }
    
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    

      this就是ThrealLocal的引用

     

      get方法会取出当前线程的ThreadLocalMap,然后调用map.getEntry方法,把本ThreadLocal的引用作为参数取出,取出map中属于ThreadLocal的value

    6.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);
        }
    

      如果之前不是空,则进行覆盖

      否则,进行创建

      this为key

    7.与线程的关系

        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

      可以发现,value保存到map中,然后又放到了线程中。

      这样就可以解释了,为什么会线程安全了。

    8.操作主要是map

      

    9.remove

         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    

      只能删除自己的数据

    五:内存泄漏问题

    1.说明

      某个对象不再使用,但是占用的内存却不能回收

    2.原因

      进行设置

        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

      然后,看到ThreadLocalMap

            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                table = new Entry[INITIAL_CAPACITY];
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                table[i] = new Entry(firstKey, firstValue);
                size = 1;
                setThreshold(INITIAL_CAPACITY);
            }
    

      看到使用Entry进行保存:

            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    

      可以发现,这个类继承了WeakReference。

      可以发现,key是弱引用的保存,value是强引用

    3.弱引用的特点

      如果这个对象被弱引用关联,又没有强引用关联,则不会阻止GC,就会被回收

    4.泄漏

      key被回收了,但是value还没有回收。

      ThreadLocalMap就不能被回收,因为每个Entry中都有一个value被强引用

      如果,线程一直在,ThreadLocalMap一直不回收,则内存泄漏了。

    5.做法

      我们需要强制删除value,这样,Entry就可以回收了,然后ThreadLocalMap就可以被回收了

      使用remove,可以删除

    6.remove

      获取本线程的ThreadLocalMap:

         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    

      进入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;
                    }
                }
            }
    

      进入clear:

        public void clear() {
            this.referent = null;
        }
    

      

    六:在Spring中的使用

    1.说明

      这里研究不深,以后看spring继续研究

    2.RequestContextHolder

    public abstract class RequestContextHolder {
        private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
        private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");
        private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");
    
        public RequestContextHolder() {
        }
    。。。。。。

      每个http对应一个线程,线程之间互相隔离

      

  • 相关阅读:
    阿里PAI深度学习组件:Tensorflow实现图片智能分类实验
    IDEA with MaxCompute Stadio
    阿里云大数据产品解决方案ODPSADSSTERAMCOMPUTEPAI介绍
    Hive DDL ROW FORMAT
    Spark-Streaming和Kafka集成指南
    网站分析指标
    网站分析概要
    前端规范大总结
    不容错过的20段CSS代码
    利用CSS实现居中对齐
  • 原文地址:https://www.cnblogs.com/juncaoit/p/12902676.html
Copyright © 2011-2022 走看看