ThreadLocal原理及应用

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()方法,避免内存泄漏。

猜你喜欢

转载自blog.csdn.net/RookieCode/article/details/107253403