zoukankan      html  css  js  c++  java
  • 深入理解ThreadLocal

    1、ThreadLocal是什么

    当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程对应的副本

    2、ThreadLocal类提供的4个方法

    • public void set(T value)

            设置当前线程的线程局部变量的值

        /**
         * Sets the current thread's copy of this thread-local variable
         * to the specified value.  Most subclasses will have no need to
         * override this method, relying solely on the {@link #initialValue}
         * method to set the values of thread-locals.
         *
         * @param value the value to be stored in the current thread's copy of
         *        this thread-local.
         */
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    • public T get()

       返回当前线程所对应的线程的局部变量

        /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        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();
        }
    • public void remove()

       将当前线程的局部变量的值删除    

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

            返回该线程局部变量的初始值,该方法是一个protected方法,显然是为了让子类覆盖而设计的,ThreadLocal中的缺省实现直接返回一个null

     protected T initialValue() {
            return null;
        }

    3、每个线程的变量副本存储在哪里

    ThreadLocal有一个静态内部类ThreadLocalMap,该内部类保存了当前线程所对应的变量的副本(需要注意的是,变量是保存在线程中的,而不是保存在ThreadLocal变量中)

    Thread类表示的当前线程中,有一个变量引用名是threadLocals,这个引用是在ThreadLocal类中createMap函数内初始化的

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

    我们所使用的ThreadLocal变量的实际数据,通过get函数实际取值的时候,就是通过取出Thread类中threadLocals引用的map,然后从这个map中根据当前threadLocal作为参数,取出数据

    4、变量副本是怎么从共享的那个变量复制出来的,ThreadLocal的初始值什么时候设置的?

    准确的说,应该是,变量副本【每个线程中保存的那个map中的变量】是怎么声明和初始化的

    从set函数可以看出,当前线程的ThreadLocalMap是在第一次调用set的时候创建map并设置相应的值的

    5、一个线程声明了n(n>=2)个这样的局部变量threadLocal,那么在Thread类中的threadLocals是怎么存储的?

    在ThreadLocal的set函数中可以看到,其中的map.set(this, value)把当前的ThreadLocal传入到map中作为键,也就是说,在不同线程的threadLocals变量中,都会有一个以你所声明的那个线程局部变量threadLocal作为键的key-value。假设你声明了n个这样的线程局部变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量作为key的键值对

    6、应用场景

    当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal

    7、典型应用

    ThreadLocal在Hibernate中的应用

    private static final ThreadLocal threadSession = new ThreadLocal();
    
            public static Session getSession() throws InfrastructureException {
                //首先判断当前线程中有没有放进去session
                Session s = (Session) threadSession.get();
                try {
                    //如果还没有,通过sessionFactory.openSession()来创建一个session,再将session放到线程中
                    //实际上是放到当前线程的ThreadLocalMap中,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap
                    if (s == null) {
                        s = getSessionFactory().openSession();
                        threadSession.set(s);
                    }
                } catch (HibernateException ex) {
                    throw new InfrastructureException(ex);
                }
                return s;
            }

    试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中

    或者自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中

     8、代码示例

    8.1 例1

    public class ThreadLocalTest {
    
        //创建一个Integer型的线程本地变量
        public static final ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return 0;
            }
        };
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[5];
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //获取当前线程的本地变量,然后累加5次
                        int num = local.get();
                        for (int j = 0; j < 5; j++) {
                            num++;
                        }
                        //重新设置累加后的本地变量
                        local.set(num);
                        System.out.println(Thread.currentThread().getName() + ":" + local.get());
                    }
                }, "Thread-" + i).start();
            }
        }
    
    }

     运行后结果:

    Thread-0:5
    Thread-3:5
    Thread-2:5
    Thread-1:5
    Thread-4:5

    可以看到,每个线程累加后的结果都是5,各个线程处理自己的本地变量值,县城之间互不影响。

    8.2 例2

    public class ThreadLocalTest {
    
        private static Index num = new Index();
    
        //创建一个Integer型的线程本地变量
        public static final ThreadLocal<Index> local = new ThreadLocal<Index>() {
            @Override
            protected Index initialValue() {
                return num;
            }
        };
    
        public static void main(String[] args) {
            Thread[] threads = new Thread[5];
            for (int i = 0; i < 5; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //获取当前线程的本地变量,然后累加5次
                        Index index = local.get();
                        for (int j = 0; j < 5; j++) {
                            index.increase();
                        }
                        //重新设置累加后的本地变量
                        local.set(index);
                        System.out.println(Thread.currentThread().getName() + ":" + local.get().num);
                    }
                }, "Thread-" + i).start();
            }
        }
    
        static class Index {
            int num;
            public void increase() {
                num++;
            }
        }
    
    }

    执行结果(每次运行结果都不一样):

    Thread-0:5
    Thread-1:20
    Thread-3:25
    Thread-4:15
    Thread-2:10

    原因:

    ThreadLocal可以给一个初始值,而每个线程获得的是这个初始值的副本,而不是要创建对象引用的副本

       public static final ThreadLocal<Index> local = new ThreadLocal<Index>() {
            @Override
            protected Index initialValue() {
                return new Index(); //注意这里
            }
        };

    改为以上方法后,正确输出结果:

    Thread-0:5
    Thread-2:5
    Thread-4:5
    Thread-3:5
    Thread-1:5

  • 相关阅读:
    团队管理 - 团队发展五阶段
    信息系统开发平台OpenExpressApp - 支持差异保存
    MDSF:Mendix介绍
    需求入门 - 获取需求方法:Nine Boxes
    个人管理 - 第四代时间管理
    需求入门 - 业务需求分析入门(公司研发峰会演讲ppt)
    个人管理 - Learn More,Study Less!
    如何培养一个人:从育儿谈起
    个人管理 - 如何演讲
    企业架构 - ADM方法概要介绍
  • 原文地址:https://www.cnblogs.com/xiaoxiao7/p/6055493.html
Copyright © 2011-2022 走看看