深入理解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

 

posted @ 2016-11-11 20:16  wencenty  阅读(478)  评论(0编辑  收藏  举报