积累知识库:ThreadLocal在工作中是怎么使用

是什么:

是java类库中的一个类,是一个线程本地变量,在多线程下,可以未每个线程维护一个变量副本,实现线程隔离,保障线程安全。

作用:

如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了线程安全问题。不是说是为了线程安全而存在,而是为了线程隔离的需求,间接保证了线程安全。

常用方法:

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("A");
System.out.println(threadLocal.get());
常用方法:
initialValue  返回此线程局部变量的初始值(单例实现)
get   返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法,则创建并初始化此副本。
set  将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能,它们只依赖于 initialValue() 方法来设置线程局部变量的值。
remove 移除此线程局部变量的值。

set时候第一次会创建map

获取map

原理:

从方法中可以看到每个线程都有一个ThreadLocalMap;

ThreadLocalMap维护了一个Entry对象,可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。每个线程在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

key:ThreadLocal对象

value:Entry( null , Object v)

内存泄漏问题:

上面源码的时候,大家有看到Entry ,这是ThreadLocalMap的内部类。而且 Entry 继承了 WeakReference ,并使用 WeakReference 对 Key 进行。所以key是个弱引用。

将ThreadLocal设为弱引用,虽然解决了ThreadLocal对象回收问题,但是对于value仍然存在问题:如果线程Thread一直被使用(线程池),导致这个引用一直在,而value就一直无法被回收,即ThreadLocalMap无法被回收

解决:每次使用完ThreadLocal,都调用它的remove()方法,清除数据

如果 Entry 的 Key 引用是强引用,就会导致 Key 引用指向的 ThreadLocal 实例及其 Value 值都不能被 GC 回收,这将造成严重的内存泄漏问题。

补充:

  • 强引用:通常new出来的对象就是强引用类型,只要引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候;
  • 软引用:使用SoftReference修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收。如果回收之后,还没有足够的内存,才会抛出内存溢出异常;
  • 弱引用:使用WeakReference修饰的对象被称为弱引用,只要发生垃圾回收,无论当前内存是否足够,都会回收掉只被弱引用关联的对象实例。
  • 虚引用:虚引用是最弱的引用,在Java中使用PhantomReference进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知。

使用实例:

1.高并发下存储数据

数据导入时候分批次导入,临时存储数据:

@PostMapping("/import1")
    public void importExcel1(MultipartFile file){
        InputStream inputStream = null;
        try {
            inputStream = file.getInputStream();
            Predicate<UserDO> predicate = item->!item.getGender().equals("1");
            UserInfoReadListener userInfoReadListener = new UserInfoReadListener(predicate, this.easyExcelService::saveBatch, 100);
            List<UserDO> objects = EasyExcel.read(inputStream).head(UserDO.class).sheet(0)
                    .registerReadListener(userInfoReadListener).doReadSync();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            if (inputStream != null){
                try {
                    inputStream.close();
                } catch (IOException e) {

                }
            }
        }
    }

监听器:

package com.study.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.study.model.entity.UserDO;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;


public class UserInfoReadListener implements ReadListener<UserDO> {


    /**
     * 条件校验
     */
    private final Predicate<UserDO> predicate;
    /**
     *消费函数
     */

    private final Consumer<List<UserDO>> consumer;

    /**
     * 批次
     */
    private final Integer batchSize;


    private ThreadLocal<List<UserDO>> cacheList;

    public UserInfoReadListener(Predicate<UserDO> predicate, Consumer<List<UserDO>> consumer, Integer batchSize) {
        this.predicate = predicate;
        this.consumer = consumer;
        this.batchSize = batchSize;
        cacheList = ThreadLocal.withInitial(ArrayList::new);

    }

    /**
     * 每次解析一条数据就会调用,可以在这里做校验
     * @param userDO
     * @param analysisContext
     */
    @Override
    public void invoke(UserDO userDO, AnalysisContext analysisContext) {
        //判断
        if (!this.predicate.test(userDO)){
            return;
        }
        //加入到集合
        this.cacheList.get().add(userDO);
        //判断是否大于某个批次
        if (this.cacheList.get().size() > this.batchSize){
            this.consumer.accept(this.cacheList.get());
            //情况缓存集合
            this.cacheList.get().clear();
        }

    }

    /**
     * 所有数据解析完毕
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        //添加剩余数据
        if (this.cacheList.get().size() > 0) {
            this.consumer.accept(this.cacheList.get());
        }

    }
}

2.存储用户登录信息


public class AuthUserContext {

    private static final ThreadLocal<UserBO> USER_INFO_IN_TOKEN_HOLDER = new TransmittableThreadLocal<>();

    public static UserBO get() {
        return USER_INFO_IN_TOKEN_HOLDER.get();
    }

    public static void set(UserBO userBO) {
        USER_INFO_IN_TOKEN_HOLDER.set(userBO);
    }

    public static void clean() {
        if (USER_INFO_IN_TOKEN_HOLDER.get() != null) {
            USER_INFO_IN_TOKEN_HOLDER.remove();
        }
    }

}

拦截器拦截请求存储用户信息:

@Component
public class LoginUserInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //获取登录的用户信息
        String accessToken = req.getHeader(tokenName);
        if (accessToken != null) {
            //把登录后用户的信息放在ThreadLocal里面进行保存
            UserBO userBO = TokenUtil.getUserInfoByAccessToken(accessToken);
            AuthUserContext.set(userBO);
            return true;
        } else {
            //未登录,返回登录页面
            response.setContentType("text/html;charset=UTF-8");
            PrintWriter out = response.getWriter();
            out.println("<script>alert('请先进行登录,再进行后续操作!')</script>");
            return false;
        }
    }
     @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        AuthUserContext.clean();
    }
}

3.JDBC链接数据库存储数据库连接



@Component
public class JdbcUtils {

    static ThreadLocal<Connection> tl = new ThreadLocal<>();

    @Autowired
    private DruidDataSource dataSource;

    // 获取连接
    /*
    * 原本: 直接从连接池中获取连接
    * 现在:
    *       1. 直接获取当前线程绑定的连接对象
    *       2. 如果连接对象是空的
    *           2.1 再去连接池中获取连接
    *           2.2 将此连接对象跟当前线程进行绑定
    * */
    public  Connection getConnection() throws SQLException {
        Connection conn = tl.get();
        if(conn == null){
            conn = dataSource.getConnection();
            tl.set(conn);
        }
        return conn;
    }

    /**
    提交关闭
    */

    public  void commitAndClose(Connection conn) {
        try {
            if(conn != null){
                //提交事务
                conn.commit();
                //移除
                tl.remove();
                //释放连接
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
    回滚
    */
    public  void rollbackAndClose(Connection conn) {
        try {
            if(conn != null){
                //回滚事务
                conn.rollback();
                //解绑当前线程绑定的连接对象
                tl.remove();
                //释放连接
                conn.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

使用

public boolean transfer() {

        Connection conn = null;
        try {
            //1. 开启事务
            conn = jdbcUtils.getConnection();
            conn.setAutoCommit(false);

            // 执行方法
            //XXXXXX
            //算术异常: 模拟转出成功,转入失败
            int i = 1 / 0;
            //2. 成功提交
            jdbcUtils.commitAndClose(conn);

        } catch (Exception e) {
            e.printStackTrace();
            //2. 或者失败回滚
            jdbcUtils.rollbackAndClose(conn);
            return false;
        }

        return true;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值