ThreadLocal,即线程变量,是一个以****ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。目的就是为了让线程能够有自己的变量
可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
1/** 2 * Sets the current thread's copy of this thread-local variable 3 * to the specified value. Most subclasses will have no need to 4 * override this method, relying solely on the {@link #initialValue} 5 * method to set the values of thread-locals. 6 * 7 * @param value the value to be stored in the current thread's copy of 8 * this thread-local. 9 */ 10 public void set(T value) { 11 12 //获取当前线程 13 Thread t = Thread.currentThread(); 14 //得到线程的ThredLocalMap 15 ThreadLocalMap map = getMap(t); 16 //如果map不为空,则将当前线程的对象作为key,传进来的参数作为value存储 17 if (map != null) 18 map.set(this, value); 19 else 20 createMap(t, value); 21 }
看一下ThredLocalMap是什么:
1static class ThreadLocalMap { 2 3 /** 4 * The entries in this hash map extend WeakReference, using 5 * its main ref field as the key (which is always a 6 * ThreadLocal object). Note that null keys (i.e. entry.get() 7 * == null) mean that the key is no longer referenced, so the 8 * entry can be expunged from table. Such entries are referred to 9 * as "stale entries" in the code that follows. 10 */ 11 static class Entry extends WeakReference<ThreadLocal<?>> { 12 /** The value associated with this ThreadLocal. */ 13 Object value; 14 15 Entry(ThreadLocal<?> k, Object v) { 16 super(k); 17 value = v; 18 } 19 } 20 21 .......
看到这是ThreadLocal的一个内部类,使用Entry类进行存储。K是我们的ThredLocal对象。
总结:Thread为每个线程维护了ThreadLocalMap这么一个Map,而ThreadLocalMap的key是LocalThread对象本身,value则是要存储的对象
再来看下get方法:
1public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t); 4 if (map != null) { 5 ThreadLocalMap.Entry e = map.getEntry(this); 6 if (e != null) { 7 @SuppressWarnings("unchecked") 8 T result = (T)e.value; 9 return result; 10 } 11 } 12 return setInitialValue(); 13 }
拿到这个entry的value。
ThreadLocal本身并不存值,它只是作为ThreadLocalMap的key,来获取value,因此能实现数据隔离。
注意:由于ThreadLocalMap的生命周期和Thread一样长,因此要手动remove掉对应的key,不然会造成内存泄露。
使用场景:
1.管理Connection,尤其是管理数据库连接。
频繁创建和关闭connection是一件很耗时的操作,因此要用到数据库连接池。ThreadLocal可以很好的管理数据库连接,因为它能够实现当前线程的操作都是用同一个Connection,保证了事务!
1public class ConnectionUtil { 2 private static Logger logger = LoggerFactory.getLogger(ConnectionUtil.class); 3 //数据库连接池 4 private static BasicDataSource dataSource; 5 //为不同的线程管理连接 6 private static ThreadLocal<Connection> local; 7 8 static { 9 BufferedReader br = null; 10 Properties ipp_prop = new Properties(); 11 12 try { 13 String propertiesurl = System.getProperty("user.dir") + "/ipp_parser.properties"; 14 br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(propertiesurl)), "utf-8")); 15 ipp_prop.load(br); 16 br.close(); 17 } catch (Exception e1) { 18 e1.printStackTrace(); 19 } 20 21 dataSource = new BasicDataSource(); 22 dataSource.setDriverClassName(ipp_prop.getProperty("db.driver")); 23 dataSource.setUrl(ipp_prop.getProperty("db.url")); 24 dataSource.setUsername(ipp_prop.getProperty("db.user")); 25 dataSource.setPassword(ipp_prop.getProperty("db.password")); 26 //初始连接 27 dataSource.setInitialSize(Integer.parseInt(ipp_prop.getProperty("db.initsize"))); 28 //最大连接 29 dataSource.setMaxTotal(Integer.parseInt(ipp_prop.getProperty("db.maxtotal"))); 30 //最长等待时间 31 dataSource.setMaxWaitMillis(Integer.parseInt(ipp_prop.getProperty("db.maxwait"))); 32 //最小空闲 33 dataSource.setMinIdle(Integer.parseInt(ipp_prop.getProperty("db.minidle"))); 34 dataSource.setMaxIdle(Integer.parseInt(ipp_prop.getProperty("db.maxidle"))); 35 //初始化线程池本地 36 local = new ThreadLocal<>();/**得到连接 37 * @return 38 * @throws SQLException 39 */ 40 public static Connection getOracleConnection() throws SQLException { 41 //获取Connection对象 42 Connection connection = dataSource.getConnection(); 43 //把Connection放进local里 44 local.set(connection); 45 logger.info("get oracleConnection"); 46 return connection; 47 } 48 49 public static void closeOracleConnection(){ 50 Connection connection = local.get(); 51 52 try { 53 if (connection != null) { 54 //设置自动提交 55 connection.setAutoCommit(true); 56 //连接还给连接池 57 connection.close(); 58 local.remove(); 59 logger.info("close oracleConnection"); 60 } 61 } catch (SQLException e) { 62 e.printStackTrace(); 63 } 64 } 65}