ThreadLocal系列(一):原理与应用

发布时间:2026/10/7 8:19:18
ThreadLocal系列(一):原理与应用 ThreadLocalThreadLocal介绍ThreadLocal提供线程局部变量每个线程独立有自己的变量副本线程之间互不干扰自己用自己的别的线程拿不到基本使用public class Basic { public static void main(String[] args) { ThreadLocalString threadLocal new ThreadLocal(); threadLocal.set(hello); System.out.println(threadLocal.get()); threadLocal.remove(); System.out.println(threadLocal.get()); } }运行结果hello null Process finished with exit code 0把hello设置到ThreadLocal中通过get方法可以获得为什么内部原理每个Thread对象内部都有一个ThreadLocalMapsetget是操作的这个map进入到Thread类中可以看到下面内容public class Thread implements Runnable { //省略。。。 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals null; //省略。。。 }再进入到ThreadLocal类中可以看到下面内容public class ThreadLocalT { //省略。。。 static class ThreadLocalMap { static class Entry extends WeakReferenceThreadLocal? { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal? k, Object v) { super(k); value v; } } private static final int INITIAL_CAPACITY 16; private Entry[] table; private int size 0; private int threshold; // Default to 0 private void setThreshold(int len) { threshold len * 2 / 3; } //省略。。。 } //省略。。。 }再来看Basic代码**threadLocal.set(hello)**实际上就是把hello作为value存到了这个map中而这个map的key是threadLocal变量与HashMap的不同Hash冲突HashMap中发生Hash冲突的时候是用链表挂起来的而ThreadLocalMap用的是开放定址。当发生冲突的时候会根据冲突的索引查找下一个空位置如果下一个位置也发生冲突继续查找下一个位置直到找到一个位置把冲突的元素挂在这个位置上。扩容HashMap的扩容因子是0.75而ThreadLocalMap的扩容因子是2/3应用上下文传递多线程执行任务的时候每个线程拿自己的用户信息互不干扰public class UserContext { private static final ThreadLocalString USER new ThreadLocal(); public static void set(String username){ USER.set(username); } public static String get(){ return USER.get(); } public static void remove(){ System.out.println(删除了用户 get()); USER.remove(); } }public class OrderService { public void createOrder(){ String username UserContext.get(); String threadName Thread.currentThread().getName(); System.out.println(用户 username 创建了订单当前线程为 threadName); } }public class UserContextTest { public static void main(String[] args) { String[] usernames {张三, 李四, 王五}; //每个线程设置用户并调用创建订单方法 for (String username : usernames) { new Thread(()-{ try { UserContext.set(username); System.out.println(当前线程 Thread.currentThread().getName() 有一个用户 username 要调用创建订单方法); new OrderService().createOrder(); } finally { UserContext.remove(); } }, thread- username).start(); } } }运行结果当前线程thread-张三有一个用户张三要调用创建订单方法 当前线程thread-李四有一个用户李四要调用创建订单方法 当前线程thread-王五有一个用户王五要调用创建订单方法 用户张三创建了订单当前线程为thread-张三 用户李四创建了订单当前线程为thread-李四 删除了用户李四 用户王五创建了订单当前线程为thread-王五 删除了用户张三 删除了用户王五线程安全化对于线程不安全的变量防止出现数据覆盖问题除了用锁等方案还可以用ThreadLocal解决每个线程内部维护自己的变量防止其他线程修改覆盖SimpleDateFormat线程安全化如果运行下面代码就会发生覆盖情况public class DateUtilUnsafe { //多个线程共享同一个SimpleDateFormat private static final SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); public static String format(Date date){ return sdf.format(date); } public static void main(String[] args) throws InterruptedException { int count 10; ExecutorService threadPool Executors.newFixedThreadPool(count); CountDownLatch latch new CountDownLatch(count); for (int i 0; i count; i) { final long timeMillis System.currentTimeMillis() i * 1000L; threadPool.submit(()-{ String format format(new Date(timeMillis)); System.out.println(Thread.currentThread().getName() 格式化时间 format); latch.countDown(); }); } latch.await(); threadPool.shutdown(); } }运行结果pool-1-thread-1格式化时间2026-10-06 10:49:10 pool-1-thread-6格式化时间2026-10-06 10:49:14 pool-1-thread-3格式化时间2026-10-06 10:49:13 pool-1-thread-7格式化时间2026-10-06 10:49:15 pool-1-thread-4格式化时间2026-10-06 10:49:13 pool-1-thread-9格式化时间2026-10-06 10:49:16 pool-1-thread-5格式化时间2026-10-06 10:49:13 pool-1-thread-10格式化时间2026-10-06 10:49:18 pool-1-thread-2格式化时间2026-10-06 10:49:10 pool-1-thread-8格式化时间2026-10-06 10:49:16可以发现有重复的13发生了覆盖情况。可以用ThreadLocal解决。public class DateUtil { private static final ThreadLocalSimpleDateFormat sdf //创建时初始化 ThreadLocal.withInitial(() - new SimpleDateFormat(yyyy-MM-dd HH:mm:ss)); public static String format(Date date){ return sdf.get().format(date); } }public class DateUtilTest { //创建多个线程格式化时间 public static void main(String[] args) throws InterruptedException { int count 10; ExecutorService threadPool Executors.newFixedThreadPool(count); CountDownLatch latch new CountDownLatch(count); for (int i 0; i count; i) { // 每个线程任务构造不同时间每个相差1秒 final long timeMillis System.currentTimeMillis() i * 1000L; threadPool.submit(()-{ try { String format DateUtil.format(new Date(timeMillis)); System.out.println(Thread.currentThread().getName() 格式化时间 format); } finally { latch.countDown(); } }); } latch.await(); threadPool.shutdown(); } }运行结果pool-1-thread-1格式化时间2026-10-06 10:38:11 pool-1-thread-10格式化时间2026-10-06 10:38:20 pool-1-thread-2格式化时间2026-10-06 10:38:12 pool-1-thread-3格式化时间2026-10-06 10:38:13 pool-1-thread-8格式化时间2026-10-06 10:38:18 pool-1-thread-4格式化时间2026-10-06 10:38:14 pool-1-thread-9格式化时间2026-10-06 10:38:19 pool-1-thread-5格式化时间2026-10-06 10:38:15 pool-1-thread-6格式化时间2026-10-06 10:38:16 pool-1-thread-7格式化时间2026-10-06 10:38:17可以看到十个线程同时执行没有发生覆盖情况因为每个线程都有自己的SimpleDateFormat互不干扰。上面的例子调用了set,get那最开始的remove是做什么的因为ThreadLocal存在内存泄漏问题需要手动调用remove防止OOM下个文章再说。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询