多线程编程中的ThreadLocal详解
保留所有版权,请引用而不是转载本文(原文地址 https://yeecode.top/blog/67/ )。
防止一个对象被多个线程同时读写是多线程编程中非常重要的工作,通常可以使用加锁等方式来实现。那有没有一种方式来彻底避免这种情况呢?
有,ThreadLocal就是其中的一种。
一个对象会被多个线程访问,是因为多个线程共享了这个对象。我们只要把对象转变为线程独有的,就可以避免这种情况。这是一种以“空间换时间”的思路:
- 当一个对象被多个线程共享时,节约了存储该对象的空间;但是在访问该对象时需要多个线程排队进行,这样便浪费了时间。
- 当我们对象设置为线程独享时,每个线程都可以无需排队自由访问对象,节约了时间;但是同一个对象可能在多个线程中存在拷贝,这样就浪费了存储空间。
当然,有一些数据在多个线程之间共享是多个线程之间通讯的需要,这种情况不在此列。
时间与空间的矛盾在程序设计中会经常出现,我们需要根据不同的场景选择不同的方案。而ThreadLocal是典型的“时间换空间”的思路,每个线程都独有一个ThreadLocal,可以在其中存放独属于该线程的数据。
ThreadLocal的主要方法有:
- T get(): 从ThreadLocal中读取数据
- void set(T value):向ThreadLocal写入数据
- void remove() :从ThreadLocal中删除数据
接下来我们通过示例展示ThreadLocal的使用,并证明ThreadLocal空间是归各个线程独享的。
在下面的代码中,我们创建threadLocalNumber和threadLocalString这两个ThreadLocal变量。
public class DemoApplication {
// 创建了两个ThreadLocal变量
private static ThreadLocal<Integer> threadLocalNumber = new ThreadLocal<>();
private static ThreadLocal<String> threadLocalString = new ThreadLocal<>();
public static void main(String[] args) {
try {
Thread thread01 = new Thread(new Task01());
Thread thread02 = new Thread(new Task02());
thread01.start();
thread02.start();
Thread.sleep(2L);
System.out.println("Main-number: " + threadLocalNumber.get());
System.out.println("Main-string: " + threadLocalString.get());
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static class Task01 implements Runnable {
@Override
public void run() {
System.out.println("Thread01-number: " + threadLocalNumber.get());
System.out.println("Set Thread01-number : 3001");
threadLocalNumber.set(3001);
System.out.println("Thread01-number: " + threadLocalNumber.get());
System.out.println("Set Thread01-string : hello thread01");
threadLocalString.set("hello thread01");
System.out.println("Thread01-string: " + threadLocalString.get());
}
}
private static class Task02 implements Runnable {
@Override
public void run() {
System.out.println("Set Thread02-number : 3002");
threadLocalNumber.set(3002);
System.out.println("Thread02-number: " + threadLocalNumber.get());
System.out.println("Thread02-string: " + threadLocalString.get());
System.out.println("Set Thread02-string : hello thread02");
threadLocalString.set("hello thread02");
System.out.println("Thread02-string: " + threadLocalString.get());
}
}
}
上述代码中一种有三个线程,分别是main方法所在的主线程、执行Task01任务的thread01线程、执行Task02任务的thread02线程。在每个线程中,我们都对这两个ThreadLocal变量进行读写操作。
运行以上代码,我们可以看到下图所示的结果。
因为是多线程并发执行,多个线程之间的打印顺序可能与上图存在差异。
通过打印出来的变量结果可知,每个线程操作的ThreadLocal变量都是线程内部的变量,不会对其他线程造成任何干扰。
在多线程程序中,当我们需要保存一些线程独有的数据时,可以借助ThreadLocal来实现。
以上内容均参考《通用源码阅读指导书——MyBatis源码详解》一书。
这是一本以MyBatis的源码为实例讲述源码阅读方法的书籍,并且附带有示例项目源码,MyBatis的全中文注解。书籍还总结了大量的编程知识和架构经验,对提升编程和架构能力十分有用。
书中的“第23章 session包”章节介绍的MyBatis中Session的创建和存储时便使用ThreadLocal实现了多个线程中连接的隔离,感兴趣的读者可以自行阅读。
可以访问个人知乎阅读更多文章:易哥(https://www.zhihu.com/people/yeecode),欢迎关注。
