wiki
提供了线程的局部变量,每个线程各自可以通过 set/get 来对自己的这个局部变量进行操作,实现了线程的数据隔离
1. 内部设计
每个 Thread
维护一个 ThreadLocalMap
,它的 key 是 ThreadLocal
本身(其实是 Entry
,是它的一个弱引用),value 是真正要存储的 Object。
每个线程往某个 ThreadLocal
里存值的时候,都存入自己的 ThreadLocalMap
里,读取也是以某个 ThreadLocal
作为引用,在自己的 map 里找对应的 key,从而实现了线程隔离。
2. 一些问题
1. ThreadLocal 做为key,而不是 Thread 做为 key 的优点
- 一个线程是可以拥有多个私有变量,key 如果是当前线程的话,还需要唯一标识 value
- 所有线程都去操作同一个 Map,其体积有可能会膨胀,导致访问性能的下降
- 当 Thread 销毁的时候,ThreadLocal 也会随之销毁,减少内存开销
2. 内存泄露
ThreadLocal
引用链如下:
ThreadLocalRef && ThreadLocalMap -> Entry key -> ThreadLocal
ThreadRef -> Thread -> ThreadLoalMap -> Entry value -> Object
其内存泄露,指使用完 ThreadLocal
, 其引用被回收,由于 ThreadLocalMap
只持有 ThreadLocal
的弱引用,所以 ThreadLocal
可以被 gc 回收,此时 Entry
中的 key=null,如果没有手动删除这个 Entry
,以及 Thread 依然在运行的前提下,value 不会被回收,而这块 value 永远不会被访问到了,导致内存泄漏
内存泄漏需要的条件:
ThreadLocal
被外部回收
- 线程被复用
- 未调用 set/get/remove
3. 实战
常用方法:
get()
: 用来获取ThreadLocal在当前线程中保存的变量副本
set(value)
: 用来设置当前线程中变量的副本
remove()
: 移除当前线程中变量的副本
initialValue()
: protected 方法,自定义初始化方法,
1 2 3 4 5 6 7 8 9 10
| private static ThreadLocal<Integer> local = new ThreadLocal<>();
private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<>() { @Override protected Integer initialValue() { return 0; } };
|
常用在数据库连接的处理中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| public static ThreadLocal<SqlSessionFactory> sessionLocal = new ThreadLocal<SqlSessionFactory>(){ @Override protected SqlSessionFactory initialValue() { return buildDefaultFactroy(); } };
public static SqlSession getSession() throws Exception { return sessionLocal.get().openSession(true); }
public static SqlSessionFactory getSessionFactory() throws Exception { return sessionLocal.get(); }
public static ThreadLocal<String> dbKeyLocal = new ThreadLocal<String>();
public static void setDBKey(String dbKey) { dbKeyLocal.set(dbKey); }
public static String getDBKey() { return dbKeyLocal.get(); }
public static SqlSessionFactory buildDefaultFactroy() { SqlSessionFactory factory = null; try { DataSource dataSource = getDataSource(); TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(serverMapper.class); ... configuration.setJdbcTypeForNull(JdbcType.NULL); factory = new SqlSessionFactoryBuilder().build(configuration); } catch (Exception e) { Logger logger = LoggerUtil.getLoggerByName(dbLogPath); logger.error("创建Mybatis连接失败", e); throw new StreamException("创建Mybatis连接失败", e); }
return factory; }
public static DruidDataSource getDataSource() throws Exception{ String masterCfgPath = "..."; ParameterTool dbParam = ParameterTool.fromPropertiesFile(masterCfgPath); String dbURL = dbParam.get("jdbc.url"); String userName = dbParam.get("jdbc.uid"); String password = dbParam.get("jdbc.pwd"); int initSize = dbParam.getInt("initialSize"); int minIdle = dbParam.getInt("minIdle"); int maxActive = dbParam.getInt("maxActive"); long maxWait = dbParam.getLong("maxWait"); long timeBetween = dbParam.getLong("timeBetweenEvictionRunsMillis"); long minTime = dbParam.getLong("minEvictableIdleTimeMillis"); boolean testIdle = dbParam.getBoolean("testWhileIdle"); boolean testBorrow = dbParam.getBoolean("testOnBorrow"); boolean testReturn = dbParam.getBoolean("testOnReturn"); String validateQuery = dbParam.get("validationQuery"); String fliters = dbParam.get("filters"); boolean pool = dbParam.getBoolean("poolPreparedStatements"); int maxPool = dbParam.getInt("maxPoolPreparedStatementPerConnectionSize");
DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(dbURL); dataSource.setUsername(userName); dataSource.setPassword(password); dataSource.setInitialSize(initSize); dataSource.setMinIdle(minIdle); dataSource.setMaxActive(maxActive); dataSource.setMaxWait(maxWait); dataSource.setTimeBetweenEvictionRunsMillis(timeBetween); dataSource.setMinEvictableIdleTimeMillis(minTime); dataSource.setTestWhileIdle(testIdle); dataSource.setTestOnBorrow(testBorrow); dataSource.setTestOnReturn(testReturn); dataSource.setValidationQuery(validateQuery); dataSource.setFilters(fliters); dataSource.setPoolPreparedStatements(pool); dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPool);
dataSource.init(); return dataSource; }
|