ThreadLocal案例和认识

本文详细介绍了Java中的ThreadLocal机制,包括其作用、编程案例、工作原理、内存泄漏问题以及应用场景。通过一个用户登录权限过滤的示例展示了ThreadLocal在多线程环境中的应用,并提醒开发者注意避免内存泄漏,强调了面试中关于ThreadLocal应掌握的知识点。
摘要由CSDN通过智能技术生成

2021年7月29日这几天是我离阿里最近的时候,因为收到了阿里本部的面试机会,技术面3轮 最后还有一个hr面.

我也发现了几个问题:

首先: 日常工作中没有准备过自己的简历

其次: 日常并没有准备面试题以及没有深入了解Java中常用框架的源码和原理等

最后: 查看网上一些人分析一面的面试题,我9成都不能回答出来,

自我感觉: 简历很难看,阿里还是挺友好的,还是给了我一次面试的机会,最终结果一面也没有过去.

加油吧!

给自己几个告诫:

  1. 记得要经常稳固自己用的东西和深入理解并记录笔记!

  1. 经常修改自己的简历,在不同招聘网站中看看自己的价值!

  1. 不要给自己太大压力,健康最重要!有钱没钱都要过下去.

  1. 为了自己喜欢的东西努力吧!

我始终有一个梦: 那就是步入大厂!

什么时候开始都不晚,此刻就是你开始的起点!

加油吧!

ThreadLocal

官方注释:

此类提供线程局部变量。这些变量不同于它们的普通对应变量,因为每个访问一个(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户 ID 或事务 ID)。例如,下面的类生成每个线程本地的唯一标识符。线程的 id 在第一次调用ThreadId.get()时被分配,并且在后续调用中保持不变。

 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<Integer> threadId =
        new ThreadLocal<Integer>() {
            @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();
    }

  }

只要线程处于活动状态并且可以访问ThreadLocal实例,每个线程都持有对其线程局部变量副本的隐式引用;线程消失后,它的所有线程本地实例副本都将进行垃圾回收(除非存在对这些副本的其他引用)。

几个问题

官方注释和使用中的几个问题:

  1. ThreadLocal是什么?

  2. ThreadLocal在一个任务中多个线程间传递相同的一个数据,如何编程使用?

  3. 多个任务间同一组业务代码,ThreadLocal如何保证数据的安全性(原理)?

  4. 线程池中使用ThreadLocal如何防止内存泄漏?

  5. 应用场景都有哪些?

  6. 面试让你说一下ThreadLocal?

1. ThreadLocal是什么?

ThreadLocal 位置处于 java.lang下,是泛型类(public class ThreadLocal<T> )

ThreadLocal 是Java中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据

2.  ThreadLocal 编程案例

案例背景: 用户登录后生成token,前端调用所有的接口会将token放置在请求头中.后端要对用户操作和接口间的权限,获取用户信息等处理,在过滤器中统一处理token后将用户信息缓存起来,以便程序可以直接获取到当前操作用户.

业务流向结构说明:

 

案例运行情况:

图片

具体代码:

 /**
  * 用户~
  *
  * @author <发哥讲Java-694204477@qq.com>
  * @version 1.0
  * @date 2021-07-29 10:29
  */
 @Data
 @AllArgsConstructor
 public class TLUser {
     /**
      * 用户ID
      */
     private Integer userId;
     /**
      * 用户姓名
      */
     private String name;
     /**
      * 用户角色 : user / admin
      * user 就是普通用户
      * admin 就是管理员,可以进行发货
      */
     private String role;
 }
 
 /**
  * 用户权限过滤~
  *
  * @author <发哥讲Java-694204477@qq.com>
  * @version 1.0
  * @date 2021-07-29 10:59
  */
 public class TLUserPermissionsFilter {
 
     public static void filter(String url) {
         TLUser loginTLUser = ThreadLocalUserUtil.getLoginTLUser();
         if ("/goods".equals(url)) {
             // 查看商品都可以通过
        }
         if ("/order".equals(url)) {
             // 订单 必须 登录
             if (null == loginTLUser) {
                 throw new RuntimeException("下订单之前必须登录");
            }
        }
         if ("/delivery".equals(url)) {
             // 查看商品都可以通过
             if (!loginTLUser.getRole().equals("admin")) {
                 throw new RuntimeException("当前账号没有权限,不能发货");
            }
        }
 
    }
 
 }
 
 
 /**
  * token 校验 过滤器~
  *
  * @author <发哥讲Java-694204477@qq.com>
  * @version 1.0
  * @date 2021-07-29 10:57
  */
 public class TokenEqualsFilter {
     public static void filter(String token, String url) {
         // token 校验判断
         if (null == token || "".equals(token)) {
             if (!"/goods".equals(url)) {
                 throw new RuntimeException("token is null or ''");
            }
        }
         ThreadLocalUserUtil.loginUser(token);
    }
 }
 
 /**
  * 用于本地缓存用户~
  *
  * @author <发哥讲Java-694204477@qq.com>
  * @version 1.0
  * @date 2021-07-29 10:36
  */
 public class ThreadLocalUserUtil {
     public static ThreadLocal<TLUser> userThreadLocal = new ThreadLocal<>();
 
     /**
      * 用来模拟 获取用户的操作
      *
      * @param token 用户token
      */
     public static void loginUser(String token) {
         // 模拟 当前用户是 普通用户
         if ("user".equals(token)) {
             userThreadLocal.set(new TLUser(1, "张三", "user"));
        }
         // 模拟 当前用户是 管理员
         if ("admin".equals(token)) {
             userThreadLocal.set(new TLUser(2, "小丽", "admin"));
        }
 
         // 模拟 游客
         if (null == token || "".equals(token)) {
             userThreadLocal.set(new TLUser(0, "游客", "游客"));
        }
    }
 
     public static TLUser getLoginTLUser() {
         return userThreadLocal.get();
    }
 
     public static void removeLoginTLUser() {
         userThreadLocal.remove();
    }
 }
 
 /**
  * 业务处理类~
  *
  * @author <发哥讲Java-694204477@qq.com>
  * @version 1.0
  * @date 2021-07-29 11:06
  */
 public class TLBiz {
 
     public static void queryGoofs() {
         if (ThreadLocalUserUtil.getLoginTLUser() == null) {
             System.out.println("游客查询");
        }
         System.out.println(ThreadLocalUserUtil.getLoginTLUser().getName() + "查询goods");
    }
 
     public static void order() {
         System.out.println(ThreadLocalUserUtil.getLoginTLUser().getName() + "下订单");
    }
 
     public static void delivery() {
         System.out.println(ThreadLocalUserUtil.getLoginTLUser().getName() + "发货");
    }
 }
 
 /**
  * threadlocal测试类~
  *
  * @author <发哥讲Java-694204477@qq.com>
  * @version 1.0
  * @date 2021-07-29 11:05
  */
 public class TestThreadlocalMain {
     public static void main(String[] args) {
         // 调用接口
 //       String url = "/goods";
 //       String url = "/order";
         String url = "/delivery";
         String token = "user";
 //       String token = "admin";
 //       String token = null;
         // 经过过滤器
         TokenEqualsFilter.filter(token, url);
         TLUserPermissionsFilter.filter(url);
         // 操作业务类
 //       TLBiz.queryGoofs();
 //       TLBiz.order();
         TLBiz.delivery();
         // 防止内存泄漏
         ThreadLocalUserUtil.removeLoginTLUser();
    }
 }
 
 

3. ThreadLocal原理

ThreadLocal底层是通过ThreadLocalMap来实现的.(ThreadLocalMap是ThreadLocal的静态内部类,Thread里面有一个变量类型是ThreadLocalMap)

每个thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值

4. 内存泄漏

如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法是,在使用了ThreadLocal对象之后,手动调用ThreadLocal的remove方法,手动清除Eentry对象.

5. 应用场景

  1. Javaweb中操作用户的缓存

  2. 连接管理(一个线程持有一个连接,该连接对象可以在不同方法之间进行传递,线程之间不共享同一个连接)

6. 说一下ThreadLocal?

1->5读上一遍,回答就可以了.

公众号发哥讲

这是一个稍偏基础和偏技术的公众号,甚至其中包括一些可能阅读量很低的包含代码的技术文,不知道你是不是喜欢,期待你的关注。

代码分享

https://gitee.com/naimaohome

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

航迹者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值