资讯详情

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

📅 2026/10/7 8:19:19 | 华诺云谱 👁 阅读
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下个文章再说。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑