是什么:
是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;
}