package com.valley.jdk.threadlocal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal threadId = new ThreadLocal() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
public static void main(String[] args) throws InterruptedException {
List tds=new ArrayList<>();
for(int i=0;i<10;i++){
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("id="+get());
}
});
tds.add(thread);
}
for(Thread td:tds){
td.start();
}
for(Thread td:tds){
td.join();
}
}
}
二、使用方法
ThreadLocal 提供了一些方法:
方法
说明
createMap(Thread t, T firstValue)
创建线程相关的线程本地,注意这个Map的key是线程本地对象,而不是线程对象
get()
返回ThreadLocal变量在这个当前线程中副本的值。
getMap(Thread t)
返回线程的ThreadLocalMap。
initialValue()
初始化重写的方法。
remove()
删除当前线程的ThreadLocal变量
set(T value)
设置当前线程这个ThreadLocal变量设置为指定的值
setInitialValue()
设初始化set
三、应用场景
ThreadLocal使用在动态数据源应用上,特别是涉及到很多的数据库时,一定会用到。
3.1动态多源数据库
定义线程本地对象
代码如下:
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
public static synchronized void setDBType(String dbType){
contextHolder.set(dbType);
}
public static String getDBType(){
return contextHolder.get();
}
public static void clearDBType(){
contextHolder.remove();
}
}
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.default.url}")
private String defaultDBUrl;
@Value("${spring.datasource.default.username}")
private String defaultDBUser;
@Value("${spring.datasource.default.password}")
private String defaultDBPassword;
@Value("${spring.datasource.default.driverClassName}")
private String defaultDBDreiverName;
@Bean
public DynamicDataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = DynamicDataSource.getInstance();
DruidDataSource defaultDataSource = new DruidDataSource();
defaultDataSource.setUrl(defaultDBUrl);
defaultDataSource.setUsername(defaultDBUser);
defaultDataSource.setPassword(defaultDBPassword);
defaultDataSource.setDriverClassName(defaultDBDreiverName);
Map map = new HashMap<>();
map.put("default", defaultDataSource);
dynamicDataSource.setTargetDataSources(map);
dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
return dynamicDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory(
@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper
DataSourceContextHolder.setDBType("default");
List list = rulesService.selectId();
if (list.size() > 0){
for (String datasourceId:list) {
TdatasourcesRules rules = rulesService.selectByKey(datasourceId);
if (rules != null || !rules.equals("")){
String url = "jdbc:mysql://" + rules.getIp() + ":" + rules.getPort() + "/" + rules.getDatabaseName() + "?useUnicode=true&characterEncoding=utf8";
DruidDataSource dynamicDataSource = new DruidDataSource();
dynamicDataSource.setDriverClassName("com.mysql.jdbc.Driver");
dynamicDataSource.setUrl(url);
dynamicDataSource.setUsername(rules.getUsername());
dynamicDataSource.setPassword(rules.getPassword());
Map dataSourceMap = DynamicDataSource.getInstance().getDataSourceMap();
dataSourceMap.put(datasourceId, dynamicDataSource);
DynamicDataSource.getInstance().setTargetDataSources(dataSourceMap);
}
}
}
}
}
具体使用切换数据源方法
代码如下:
public void doEsAndDataTask(String dbType){
//切换动态数据源
DataSourceContextHolder.setDBType(dbType);
List list = didMsgService.queryPart();
//清除线程本地变量的副本,一般数据库链接都会用到线程池,所以要清除
DataSourceContextHolder.clearDBType();
}
重点:除了每个线程维护一个ThreadLocalMap,我们这里也需要维护一个数据源对象Map,数据源对象Map的key是我们的每个数据库ID,ThreadLocalMap的值是数据库ID。具体两者的映射逻辑交给了Spring-jdbc来处理的。 在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收,避免内存泄漏。 所以setDBType和clearDBType是成对出现。
四、源码剖析
ThreadLocal的初始化可以匿名内部类重写initialValue方法,也可以直接定义。
private static final ThreadLocal contextHolder = new ThreadLocal<>();
我们通过获取本地变量,来剖析下源码
获取ThreadLocalMap,然后通过ThreadLocal对象this作为Key获取值返回
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();
}