ThreadLocal原理及应用
ThreadLocal是一个关于创建线程局部变量的类,是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。
ThreadLocal 是一个泛型类,可以接受任何类型的对象。
核心方法源码
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();
}
set()方法
/**
* 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);
}
remove()方法
/**
* 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);
}
createMap方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap是个静态的内部类
static class ThreadLocalMap {
............................
}
最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。
内存泄漏问题
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。
所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。
ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。
使用场景
ThreadLocal 适用于如下两种场景
1.每个线程需要有自己单独的实例
2.实例需要在多个方法中共享,但不希望被多线程共享
我在用到ThreadLocal的场景需求是第二种
代码:
@Autowired
private MongoDBServiceImpl mongoDBService;
private static ThreadLocal<List<Map<String, Object>>> threadLocal = new ThreadLocal();
@Override
public Map<String, List<Map<String, Object>>> excelUpload(MultipartFile uploadFile) {
Map<String, List<Map<String, Object>>> map = new HashMap<>();
List<Map<String, Object>> list = new ArrayList<>();
try {
map.putAll(ExcelTool.readExcel(uploadFile));
for (Map.Entry<String, List<Map<String, Object>>> entry:map.entrySet()){
list.addAll(entry.getValue());
}
threadLocal.set(list);
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
@Override
public Map insert(Map<String, Object> map) {
if (map.containsKey("type") && map.get("type") != null && "all".equals(map.get("type"))){
map.put("set",threadLocal.get());
threadLocal.remove();
}
Map<String, Object> result = new HashMap<>();
List<Map<String, Object>> oldList = new ArrayList<>();
List<Map<String, Object>> newList = new ArrayList<>();
List<Map<String, Object>> insertList = new ArrayList<>();
List<Map<String, Object>> newModifyList = new ArrayList<>();
List<Map<String, Object>> oldModifyList = new ArrayList<>();
List<Map<String, Object>> neo4jList = new ArrayList<>();
List<Document> documents = new ArrayList<>();
Map<String, Object> oldMaps = mongoDBService.findAllByCollectionName(map);
if (oldMaps.containsKey("list") && oldMaps.get("list") != null && oldMaps.get("list") instanceof List){
oldList = (List<Map<String, Object>>) oldMaps.get("list");
}
if (map.containsKey("set") && map.get("set") != null && map.get("set") instanceof List){
newList = (List<Map<String, Object>>) map.get("set");
}
if (oldList != null && oldList.size() > 0){
for (Map<String, Object> oldMap:oldList){
oldMap.remove("_id");
}
for (Map<String, Object> newMap:newList){
if (!oldList.contains(newMap)){
for (Map<String, Object> oldMap:oldList){
if (compare(newMap,oldMap)){
newModifyList.add(newMap);
oldModifyList.add(oldMap);
}
}
}
}
newList.removeAll(newModifyList);
List<Map<String, Object>> temporary = new ArrayList<>(newList);
for (Map<String, Object> newMap:newList){
if (oldList.contains(newMap)){
temporary.remove(newMap);
}
}
insertList.addAll(temporary);
}else {
insertList.addAll(newList);
}
for (Map<String, Object> insert:insertList){
Map<String, Object> neo4j = new HashMap<>();
neo4j.put("name",insert.get("name"));
neo4j.put("type",insert.get("type"));
neo4jList.add(neo4j);
Document document = new Document();
for (Map.Entry<String, Object> entry:insert.entrySet()){
if (!"id".equals(entry.getKey()) && !"labels".equals(entry.getKey()) && !"props".equals(entry.getKey()) && entry.getValue() != null && !"".equals(entry.getValue())){
document.put(entry.getKey(),entry.getValue());
}
}
documents.add(document);
}
if (insertList != null && insertList.size()>0){
//存入MongoDB
mongoDBService.getDBCollection(map.get("collectionName").toString()).insertMany(documents);
String collectionName = "newNode";
if (map.containsKey("collectionName") && null != map.get("collectionName") && !"".equals(map.get("collectionName"))){
collectionName = map.get("collectionName").toString();
}
String cypher = "UNWIND {list} as row CREATE (n:`"+collectionName+"`) SET n=row.props";
//存入Neo4j
DatabaseUtil.neo4jExecutes(cypher, NodeUtil.setProps(neo4jList));
}
result.put("newModifyList",newModifyList);
result.put("oldModifyList",oldModifyList);
return result;
}
第二个接口直接调用第一个接口中的数据省掉前端数据传入过程。
注意:ThreadLocal实例最好用static修饰,实例用完之后调用remove()方法,避免内存泄漏。