threadlocal 订阅
ThreadLocal是Thread的局部变量,用于编多线程程序,对解决多线程程序的并发问题有一定的启示作用。 展开全文
ThreadLocal是Thread的局部变量,用于编多线程程序,对解决多线程程序的并发问题有一定的启示作用。
信息
定    位
局部变量
版    本
JDK 1.2
作    用
提供了一种新的思路
中文名
线程局部变量
外文名
ThreadLocal
ThreadLocal介绍
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。ThreadLocal的接口方法ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:void set(Object value)public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 1.5 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
收起全文
精华内容
参与话题
问答
  • ThreadLocal

    千次阅读 2015-07-22 16:56:13
    ThreadLocal

    ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
    在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。ThreadLocal中有四个方法:
    1、get()方法是用来获取ThreadLocal在当前线程中保存的变量副本
    2、set()用来设置当前线程中变量的副本
    3、remove()用来移除当前线程中变量的副本
    4、initialValue()是一个protected方法,显然是为了让子类覆盖而设计的,返回此线程局部变量的当前线程的初始值

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

    ThreadLocal的set()方法如上,首先通过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,然后将变量的值设置到这个ThreadLocalMap对象中,当然如果获取到的ThreadLocalMap对象为空,就通过createMap方法创建。ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象。

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

    在getMap中,是调用当前线程t,返回当前线程t中的一个成员变量threadLocals。
    初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

    在进行get之前,必须先set,否则会报空指针异常;因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
        }
    
    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;
        }

    很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

    展开全文
  • 深挖ThreadLocal

    万次阅读 多人点赞 2014-04-22 16:59:27
    ThreadLocal是什么 早在JDK 1.2的版本中就提供java.lang.ThreadLocalThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。 ThreadLocal很容易让人...

    先总述,后分析

      深挖过threadLocal之后,一句话概括:Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。所以ThreadLocal的应用场合,最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

      数据隔离的秘诀其实是这样的,Thread有个TheadLocalMap类型的属性,叫做threadLocals,该属性用来保存该线程本地变量。这样每个线程都有自己的数据,就做到了不同线程间数据的隔离,保证了数据安全。

      接下来采用jdk1.8源码进行深挖一下TheadLocal和TheadLocalMap。

    ThreadLocal是什么

      早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

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

      从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

      所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。

     

    原理

      ThreadLocal,连接ThreadLocalMap和Thread。来处理Thread的TheadLocalMap属性,包括init初始化属性赋值、get对应的变量,set设置变量等。通过当前线程,获取线程上的ThreadLocalMap属性,对数据进行get、set等操作。

      ThreadLocalMap,用来存储数据,采用类似hashmap机制,存储了以threadLocal为key,需要隔离的数据为value的Entry键值对数组结构。

      ThreadLocal,有个ThreadLocalMap类型的属性,存储的数据就放在这儿。

    ThreadLocal、ThreadLocal、Thread之间的关系

      ThreadLocalMap是ThreadLocal内部类,由ThreadLocal创建,Thread有ThreadLocal.ThreadLocalMap类型的属性。源码如下:

    Thread的属性:

    public
    class Thread implements Runnable {
        /*...其他属性...*/
    
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    ThreadLocal和ThreadLocalMap

    public class ThreadLocal<T> {
        /**..其他属性和方法稍后介绍...*/
        /**
         * ThreadLocalMap is a customized hash map suitable only for
         * maintaining thread local values. No operations are exported
         * outside of the ThreadLocal class. The class is package private to
         * allow declaration of fields in class Thread.  To help deal with
         * very large and long-lived usages, the hash table entries use
         * WeakReferences for keys. However, since reference queues are not
         * used, stale entries are guaranteed to be removed only when
         * the table starts running out of space.
         */
        static class ThreadLocalMap {

    由ThreadLocal对Thread的TreadLocalMap进行赋值

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

    ThreadLocal的接口方法

    ThreadLocal类核心方法set、get、initialValue、withInitial、setInitialValue、remove:

       /**
         * Returns the current thread's "initial value" for this
         * thread-local variable.  This method will be invoked the first
         * time a thread accesses the variable with the {@link #get}
         * method, unless the thread previously invoked the {@link #set}
         * method, in which case the {@code initialValue} method will not
         * be invoked for the thread.  Normally, this method is invoked at
         * most once per thread, but it may be invoked again in case of
         * subsequent invocations of {@link #remove} followed by {@link #get}.
         *
         * <p>This implementation simply returns {@code null}; if the
         * programmer desires thread-local variables to have an initial
         * value other than {@code null}, {@code ThreadLocal} must be
         * subclassed, and this method overridden.  Typically, an
         * anonymous inner class will be used.
         *
         * @return the initial value for this thread-local
         */
        protected T initialValue() {
            return null;
        }
    
        /**
         * Creates a thread local variable. The initial value of the variable is
         * determined by invoking the {@code get} method on the {@code Supplier}.
         *
         * @param <S> the type of the thread local's value
         * @param supplier the supplier to be used to determine the initial value
         * @return a new thread local variable
         * @throws NullPointerException if the specified supplier is null
         * @since 1.8
         */
        public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
            return new SuppliedThreadLocal<>(supplier);
        }
    
        /**
         * Creates a thread local variable.
         * @see #withInitial(java.util.function.Supplier)
         */
        public ThreadLocal() {
        }
    
        /**
         * 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();
        }
    
        /**
         * Variant of set() to establish initialValue. Used instead
         * of set() in case user has overridden the set() method.
         *
         * @return the initial value
         */
        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;
        }
    
        /**
         * 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);
        }
    
        /**
         * Removes the current thread's value for this thread-local
         * variable.  If this thread-local variable is subsequently
         * {@linkplain #get read} by the current thread, its value will be
         * reinitialized by invoking its {@link #initialValue} method,
         * unless its value is {@linkplain #set set} by the current thread
         * in the interim.  This may result in multiple invocations of the
         * {@code initialValue} method in the current thread.
         *
         * @since 1.5
         */
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    • initialValue返回该线程局部变量的初始值。该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
    • withInitial提供一个Supplier的lamda表达式用来当做初始值,java8引入。
    • setInitialValue设置初始值。在get操作没有对应的值时,调用此方法。private方法,防止被覆盖。过程和set类似,只不过是用initialValue作为value进行设置。
    • set设置当前线程对应的线程局部变量的值。先取出当前线程对应的threadLocalMap,如果不存在则用创建一个,否则将value放入以this,即threadLocal为key的映射的map中,其实threadLocalMap内部和hashMap机制一样,存储了Entry键值对数组,后续会深挖threadLocalMap。
    • get该方法返回当前线程所对应的线程局部变量。和set类似,也是先取出当前线程对应的threadLocalMap,如果不存在则用创建一个,但是是用inittialValue作为value放入到map中,且返回initialValue,否则就直接从map取出this即threadLocal对应的value返回。
    • remove将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。需要注意的是如果remove之后又调用了get,会重新初始化一次,即再次调用initialValue方法,除非在get之前调用set设置过值。

    ThreadLocalMap简介

      看名字就知道是个map,没错,这就是个hashMap机制实现的map,用Entry数组来存储键值对,key是ThreadLocal对象,value则是具体的值。值得一提的是,为了方便GC,Entry继承了WeakReference,也就是弱引用。里面有一些具体关于如何清理过期的数据、扩容等机制,思路基本和hashmap差不多,有兴趣的可以自行阅读了解,这边只需知道大概的数据存储结构即可。

        /**
         * ThreadLocalMap is a customized hash map suitable only for
         * maintaining thread local values. No operations are exported
         * outside of the ThreadLocal class. The class is package private to
         * allow declaration of fields in class Thread.  To help deal with
         * very large and long-lived usages, the hash table entries use
         * WeakReferences for keys. However, since reference queues are not
         * used, stale entries are guaranteed to be removed only when
         * the table starts running out of space.
         */
        static class ThreadLocalMap {
    
            /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }

     

    Thread同步机制的比较

     

      ThreadLocal和线程同步机制相比有什么优势呢?

      Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

      在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

      而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

      概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

      Spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,因为有状态的Bean就可以在多线程中共享了。

      一般的Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程。

     

      同一线程贯通三层这样你就可以根据需要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有关联的对象引用到的都是同一个变量。

      下面的实例能够体现Spring对有状态Bean的改造思路:

    代码清单3 TestDao:非线程安全

     

    package com.test;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestDao {
    	private Connection conn;// ①一个非线程安全的变量
    
    	public void addTopic() throws SQLException {
    		Statement stat = conn.createStatement();// ②引用非线程安全变量
    		// …
    	}
    }

     

     

     

    由于①处的conn是成员变量,因为addTopic()方法是非线程安全的,必须在使用时创建一个新TopicDao实例(非singleton)。下面使用ThreadLocal对conn这个非线程安全的“状态”进行改造:

    代码清单4 TestDao:线程安全

     

    package com.test;
    
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.sql.Statement;
    
    public class TestDaoNew {// ①使用ThreadLocal保存Connection变量
      private static ThreadLocal<Connection> connThreadLocal = ThreadLocal.withInitial(Test::createConnection);
    
      // 具体创建数据库连接的方法
      private static Connection createConnection() {
        Connection result = null;
        /**
         * create a real connection...
         * such as :
         * result = DriverManager.getConnection(dbUrl, dbUser, dbPwd);
         */
        return result;
      }
    
      // ③直接返回线程本地变量
      public static Connection getConnection() {
        return connThreadLocal.get();
      }
    
      // 具体操作
      public void addTopic() throws SQLException {
        // ④从ThreadLocal中获取线程对应的Connection
        Statement stat = getConnection().createStatement();
        //....any other operation
      }
    }

     

      不同的线程在使用TopicDao时,根据之前的深挖get具体操作,判断connThreadLocal.get()会去判断是有map,没有则根据initivalValue创建一个Connection对象并添加到本地线程变量中,initivalValue对应的值也就是上述的lamba表达式对应的创建connection的方法返回的结果,下次get则由于已经有了,则会直接获取已经创建好的Connection,这样,就保证了不同的线程使用线程相关的Connection,而不会使用其它线程的Connection。因此,这个TopicDao就可以做到singleton共享了。

     

      当然,这个例子本身很粗糙,将Connection的ThreadLocal直接放在DAO只能做到本DAO的多个方法共享Connection时不发生线程安全问题,但无法和其它DAO共用同一个Connection,要做到同一事务多DAO共享同一Connection,必须在一个共同的外部类使用ThreadLocal保存Connection。


     

    ConnectionManager.java

    package com.test;
    
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    
    public class ConnectionManager {
    
    	private static ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {
    		Connection conn = null;
    		try {
    			conn = DriverManager.getConnection(
    					"jdbc:mysql://localhost:3306/test", "username",
    					"password");
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    		return conn;
    	});
    
    	public static Connection getConnection() {
    		return connectionHolder.get();
    	}
    }

     

    线程隔离的秘密

     

    秘密就就在于上述叙述的ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。


    为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:

     

     

     

     

        /**
         * Get the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param  t the current thread
         * @return the map
         */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
        /**
         * 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
         * @param map the map to store.
         */
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }

     

     

     

    小结

      ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

     

    后记

      看到网友评论的很激烈,甚至关于ThreadLocalMap不是ThreadLocal里面的,而是Thread里面的这种评论都出现了,于是有了这个后记,下面先把jdk源码贴上,源码最有说服力了。

     

    /**
         * ThreadLocalMap is a customized hash map suitable only for
         * maintaining thread local values. No operations are exported
         * outside of the ThreadLocal class. The class is package private to
         * allow declaration of fields in class Thread.  To help deal with
         * very large and long-lived usages, the hash table entries use
         * WeakReferences for keys. However, since reference queues are not
         * used, stale entries are guaranteed to be removed only when
         * the table starts running out of space.
         */
        static class ThreadLocalMap {...}

      源码就是以上,这源码自然是在ThreadLocal里面的,有截图为证。

     

      本文是自己在学习ThreadLocal的时候,一时兴起,深入看了源码,思考了此类的作用、使用范围,进而联想到对传统的synchronize共享变量线程安全的问题进行比较,而总结的博文,总结一句话就是一个是锁机制进行时间换空间,一个是存储拷贝进行空间换时间。

     

    (全文完)

     

     

     

     

     

    展开全文
  • Threadlocal

    千次阅读 多人点赞 2017-11-27 10:22:36
    想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理。首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给...
    想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理。首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两个应用场景。
    

    一.对ThreadLocal的理解

            ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

            这句话从字面上看起来很容易理解,但是真正理解并不是那么容易。

            我们还是先来看一个例子:

    class ConnectionManager {  
       
     private static Connection connect = null;  
       
     public static Connection openConnection() {  
     if(connect == null){  
     connect = DriverManager.getConnection();  
     }  
     return connect;  
     }  
       
     public static void closeConnection() {  
     if(connect!=null)  
     connect.close();  
     }  
    }  

    假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。

            所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。

            这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。

            那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。

            到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:

    class ConnectionManager {  
       
     private Connection connect = null;  
       
     public Connection openConnection() {  
     if(connect == null){  
     connect = DriverManager.getConnection();  
     }  
     return connect;  
     }  
       
     public void closeConnection() {  
     if(connect!=null)  
     connect.close();  
     }  
    }  
       
    class Dao{  
     public void insert() {  
     ConnectionManager connectionManager = new ConnectionManager();  
     Connection connection = connectionManager.openConnection();  
       
     //使用connection进行操作  
       
     connectionManager.closeConnection();  
     }  
    }  

      这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不仅严重影响程序执行效率,还可能导致服务器压力巨大。

            那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

            但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

     

    二.深入解析ThreadLocal类

            在上面谈到了对ThreadLocal的一些理解,那我们下面来看一下具体ThreadLocal是如何实现的。

            先了解一下ThreadLocal类提供的几个方法:


    public T get() { }  
    public void set(T value) { }  
    public void remove() { }  
    protected T initialValue() { }  

    get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。

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

      第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。

            如果获取成功,则返回value值。

            如果map为空,则调用setInitialValue方法返回value。

            我们上面的每一句来仔细分析:

            首先看一下getMap方法中做了什么:


        /**
         * Get the map associated with a ThreadLocal. Overridden in
         * InheritableThreadLocal.
         *
         * @param  t the current thread
         * @return the map
         */
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }

    可能大家没有想到的是,在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

            那么我们继续取Thread类中取看一下成员变量threadLocals是什么:

     /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:

        /**
         * ThreadLocalMap is a customized hash map suitable only for
         * maintaining thread local values. No operations are exported
         * outside of the ThreadLocal class. The class is package private to
         * allow declaration of fields in class Thread.  To help deal with
         * very large and long-lived usages, the hash table entries use
         * WeakReferences for keys. However, since reference queues are not
         * used, stale entries are guaranteed to be removed only when
         * the table starts running out of space.
         */
        static class ThreadLocalMap {
    
            /**
             * The entries in this hash map extend WeakReference, using
             * its main ref field as the key (which is always a
             * ThreadLocal object).  Note that null keys (i.e. entry.get()
             * == null) mean that the key is no longer referenced, so the
             * entry can be expunged from table.  Such entries are referred to
             * as "stale entries" in the code that follows.
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }

      可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

            然后再继续看setInitialValue方法的具体实现:

        /**
         * Variant of set() to establish initialValue. Used instead
         * of set() in case user has overridden the set() method.
         *
         * @return the initial value
         */
        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;
        }

    很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map,看一下createMap的实现:

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

    至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:

            首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

            初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

            然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

            下面通过一个例子来证明通过ThreadLocal能达到在每个线程中创建变量副本的效果:

    package com.milo.jdk.lang;
    /**
     * You need to setting before getting it, otherwise there will be a NullPointerException
     * @author MILO
     *
     */
    public class MiloTheadLocal {
    	  	ThreadLocal<Long> longLocal = new ThreadLocal<Long>();  
    	    ThreadLocal<String> stringLocal = new ThreadLocal<String>();  
    	  
    	    public void set() {  
    	    	longLocal.set(Thread.currentThread().getId());
    	    	stringLocal.set(Thread.currentThread().getName());
    	    }  
    	  
    	    public long getLong() {  
    	        return longLocal.get();  
    	    }  
    	  
    	    public String getString() {  
    	        return stringLocal.get();  
    	    }  
    	  
    	    public static void main(String[] args) throws InterruptedException {  
    	        final MiloTheadLocal test = new MiloTheadLocal();  
    	  
    	        test.set();  
    	        System.out.println(test.getLong());  
    	        System.out.println(test.getString());  
    	  
    	        
    	        Thread thread=new Thread() {
    	        	public void run() {
    	        		test.set();
    	        		 System.out.println(test.getLong());  
    	        		 System.out.println(test.getString());  
    	        	}
    	        };
    	        thread.start();
    	        //thread.join():用来指定当前主线程等待其他线程执行完毕后,再来继续执行Thread.join()后面的代码
    	        thread.join();
    	        System.out.println(test.getLong());  
    	        System.out.println(test.getString());  
    	    }  
    }
    

    运行结果:

    1
    main
    10
    Thread-0
    1
    main
    

    从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。

            总结一下:

            1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

            2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

            3)在进行get之前,必须先set,否则会报空指针异常;

            如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

            因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

        /**
         * 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();
        }
        /**
         * Variant of set() to establish initialValue. Used instead
         * of set() in case user has overridden the set() method.
         *
         * @return the initial value
         */
        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;
        }
    

     看下面这个例子:


    package com.milo.jdk.lang;
    /**
     * You need to setting before getting it, otherwise there will be a NullPointerException
     * @author MILO
     *
     */
    public class MiloTheadLocal {
    	  	ThreadLocal<Long> longLocal = new ThreadLocal<Long>();  
    	    ThreadLocal<String> stringLocal = new ThreadLocal<String>();  
    	  
    	    public void set() {  
    	    	longLocal.set(Thread.currentThread().getId());
    	    	stringLocal.set(Thread.currentThread().getName());
    	    }  
    	  
    	    public long getLong() {  
    	        return longLocal.get();  
    	    }  
    	  
    	    public String getString() {  
    	        return stringLocal.get();  
    	    }  
    	  
    	    public static void main(String[] args) throws InterruptedException {  
    	        final MiloTheadLocal test = new MiloTheadLocal();  
    	  
    	        //test.set();  
    	        System.out.println(test.getLong());  
    	        System.out.println(test.getString());  
    	  
    	        
    	        Thread thread=new Thread() {
    	        	public void run() {
    	        		test.set();
    	        		 System.out.println(test.getLong());  
    	        		 System.out.println(test.getString());  
    	        	}
    	        };
    	        thread.start();
    	        //thread.join():用来指定当前主线程等待其他线程执行完毕后,再来继续执行Thread.join()后面的代码
    	        thread.join();
    	        System.out.println(test.getLong());  
    	        System.out.println(test.getString());  
    	    }  
    }
    
    运行结果:
    Exception in thread "main" java.lang.NullPointerException
    	at com.milo.jdk.lang.MiloTheadLocal.getLong(MiloTheadLocal.java:17)
    	at com.milo.jdk.lang.MiloTheadLocal.main(MiloTheadLocal.java:28)

      在main线程中,没有先set,直接get的话,运行时会报空指针异常。

            但是如果改成下面这段代码,即重写了initialValue方法:

    package com.milo.jdk.lang;
    /**
     * You not need to setting before getting it.
     * @author MILO
     *
     */
    public class MiloTheadLocal2 {
    	   ThreadLocal<Long> longLocal = new ThreadLocal<Long>() {  
    	        protected Long initialValue() {  
    	            return Thread.currentThread().getId();  
    	        };  
    	    };  
    	      
    	    ThreadLocal<String> stringLocal = new ThreadLocal<String>() {  
    	        protected String initialValue() {  
    	            return Thread.currentThread().getName();  
    	        };  
    	    };  
    	  
    	    public void set() {  
    	        longLocal.set(Thread.currentThread().getId());  
    	        stringLocal.set(Thread.currentThread().getName());  
    	    }  
    	  
    	    public long getLong() {  
    	        return longLocal.get();  
    	    }  
    	  
    	    public String getString() {  
    	        return stringLocal.get();  
    	    }  
    	  
    	    public static void main(String[] args) throws InterruptedException {  
    	        final MiloTheadLocal2 test = new MiloTheadLocal2();  
    	  
    	        //test.set();  
    	        System.out.println(test.getLong());  
    	        System.out.println(test.getString());  
    	  
    	        Thread thread1 = new Thread() {  
    	            public void run() {  
    	                //test.set();  
    	                System.out.println(test.getLong());  
    	                System.out.println(test.getString());  
    	            };  
    	        };  
    	        thread1.start();  
    	        thread1.join();  
    	  
    	        System.out.println(test.getLong());  
    	        System.out.println(test.getString());  
    	    }  
    }
    

    运行结果:

    1  
    main  
    8  
    Thread-0  
    1  
    main  


    三.ThreadLocal的应用场景

            最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。如:

            数据库连接:

    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
        public Connection initialValue() {  
            return DriverManager.getConnection(DB_URL);  
        }  
    };  
      
    public static Connection getConnection() {  
        return connectionHolder.get();  
    }  

     Session管理:


    private static final ThreadLocal threadSession = new ThreadLocal();  
      
    public static Session getSession() throws InfrastructureException {  
        Session s = (Session) threadSession.get();  
        try {  
            if (s == null) {  
                s = getSessionFactory().openSession();  
                threadSession.set(s);  
            }  
        } catch (HibernateException ex) {  
            throw new InfrastructureException(ex);  
        }  
        return s;  
    }  






    展开全文
  • threadlocal

    2018-08-08 17:05:35
    ThreadLocal 提供了get与set等访问接口和方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。 ThreadLocal对象通常用于防止对可变的单实例变量...

    ThreadLocal 提供了get与set等访问接口和方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。

    ThreadLocal对象通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。例如,在单线程应用程序中可能会维持一个全局的数据库连接,并在程序启动时初始化这个连接对象,从而避免在调用每个方法时都要传递一个Connection对象。由于JDBC的连接对象不一定是线程安全的,因此,当多线程应用程序在没有协同的情况下使用全局变量时,就不是线程安全的,代码如下:

    private static ThreadLocal<Connection> connectionHolder 
        = new ThreadLocal<Connection>() {
            public Connection initialValue(){
                return DriverManager.getConnection(DB_URL);
            }
        };
    public static Connection getConnection(){
        return connectionHolder.get();
    }

    当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用这项技术。例如,在Java5.0之前,Integer.toString()方法使用ThreadLocal对象来保存一个12字节大小的缓冲区,用于对结果进行格式化,而不是使用共享的静态缓冲区(这需要锁机制)或者在每次调用时都分配一个新的缓冲区。

    关于ThreadLocal内存泄漏问题,可参考此文http://www.importnew.com/22039.html

    InheritableThreadLocal类可以让子线程从父线程中取值。在使用InheritableThreadLocal类需要注意的一点是,如果子线程在取值的同时,主线程对InheritableThreadLocal中的值进行修改,那么子线程取到的值仍是旧值。

    展开全文

空空如也

1 2 3 4 5 ... 20
收藏数 21,935
精华内容 8,774
关键字:

threadlocal