文章目录
一、简单的使用
1.1 是什么
ThreadLocal是一个线程的局部变量,只有当前线程可以访问,因为只有当前线程可以访问,所以是线程安全的。
1.2 示例
1、举例
多线程环境中使用SimpleDateFormat 解析日期
2、代码
public class SimpleDateFormatTest {
// SimpleDateFormat 实例
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 解析日期任务类
public static class ParseDate implements Runnable {
private int i = 0;
public ParseDate(int i) {this.i = i;}
@Override
public void run() {
try {
Date date = sdf.parse("2020-10-06 19:21:" + i % 60);
System.out.println(i + " : " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建线程池,解析日期
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.execute(new ParseDate(i));
}
}
}
3、执行结果
Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-1" java.lang.NumberFormatException: multiple points
可以看到执行后抛出异常,所以SimpleDateFormat.parse()方法不是线程安全的
1.3 解决方案
方案一:在parse方法前后加锁
方案二:使用ThreadLocal
/**
* @author QianYi
* @version 1.0
* @Description 使用ThreadLocal解析日期
*/
public class x02ParseDate {
// SimpleDateFormat 实例
private static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<>();
// 日期解析任务类
public static class ParseDate implements Runnable {
private int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
// 如果ThreadLocal中没有SimpleDateFormat实例,那就初始化一个
if (tl.get() == null) {
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
Date date = tl.get().parse("2020-10-06 19:21:" + i % 60);
System.out.println(i + " : " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 创建线程池,解析日期
ExecutorService pool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
pool.execute(new ParseDate(i));
}
}
}
为每一个线程分配不同的对象,需要在应用层进行保证;ThreadLocal只是起到了容器的作用
二、实现原理
2.1 set() 方法
我们来看看 set() 方法的内部
public void set(T value) {
// 1、获取当前线程对象
Thread t = Thread.currentThread();
// 2、获取当前线程对象的 ThreadlLocalMap
ThreadLocalMap map = getMap(t);
// 3、将值存入 ThreadlLcaolMap 中
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
1、可以简单地把ThreadlLocalMap看作是一个Map(但请注意,它不是HashMap,而是存储 key-value 结构的 Entry)。
2、ThreadlLocalMap 是定义在Thread类中地成员,如下代码所示:
ThreadLocal.ThreadLocalMap threadLocals = null; // Thread 类中
我们设置的数据写入了 threadLocals 中,其中 key 为 ThreadLocal 当前对象,value 就是我们设置的值。threadLocals 保存了当前自己线程所有的“局部变量”,也就是一个ThreadLocal变量的集合。
2.2 get() 方法
public T get() {
// 1、获取当前线程
Thread t = Thread.currentThread();
// 2、获取当前线程的 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 3、将自己作为 key 取得内部的数据
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
return (T) e.value;
}
}
return setInitialValue();
}
ThreadLocal 中的一些变量是维护在Threa类内部的,所以:只要线程不退出,对象的引用就一直存在,无法被GC回收。
当线程退出时,Thread类会进行一些清理操作(包括清理ThreadLocalMap)。通过将引用置为null,使得JVM将其当作垃圾并进行回收
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
// 加速资源清理
target = null;
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
2.3 ThreadLocal 导致内存泄漏
1、问题
如上面所说,线程不退出,对象引用就一直存在,这在线程池中可能会导致内存泄漏。
例如固定大小的线程池(FixThreadPool),其线程总是存在。如果将一些大对象设置进了 ThreadLocal 中,使用了几次后就不再使用,同时也没有清理它,这会导致大对象无法被回收,最终导致内存泄露。
2、如何解决
- 使用 ThreadLocal.remove() 方法移除这个变量
- 使用类似于
obj = null
的代码
3、为何使用类似于 obj = null
的代码可以防止内存泄漏
- ThreadLocalMap使用了弱引用(弱引用就是:在垃圾回收时,JVM 发现弱引用,就直接将其回收)。
- ThreadLocalMap 内部由一系列 Entry 构成,每个Entry都继承了弱引用 WeakRefefence,如下代码所示:
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
其中 k 是 ThreadLocal 实例,作为弱引用使用。super(k)就是调用了 WeakReference 的构造函数;
因此虽然使用 ThreadLocal 作为 Entry 的 k,但实际上它并不持有 ThreadLocal 的引用。
当 ThreadLocal 的外部强引用被回收时,Entry 中的 k 就会变成 null。
当进行垃圾回收时,就会将这些垃圾数据回收。
三、对性能的帮助
3.1 说明
如果共享对象对于竞争的处理容易引起性能损失,我们还是考虑使用ThreadLocal为每个线程分配单独的对象
3.2 代码示例
我们使用一个随机数的例子来说明性能。
- 定义全局变量
// 要生成的随机数数量
private static final int GEN_COUNT = 10000000;
// 线程数量
private static final int THREAD_NUM = 4;
// 线程池
private static ExecutorService pool = Executors.newFixedThreadPool(THREAD_NUM);
// 多线程共享的 Random 实例
private static Random rnd = new Random(123);
// ThreadLocal 封装的 Random 实例
private static ThreadLocal<Random> tRnd = new ThreadLocal<Random>() {
@Override
protected Random initialValue() {
return new Random(123);
}
};
- 定义任务类
public static class RndTask implements Callable<Long> {
// 0:使用多线程共享的 Random 的实例
// 1:使用 ThreadLocal 封装的 Random 实例
private int mode;
public RndTask(int mode) {
if (mode != 0 || mode != 1) throw new RuntimeException();
this.mode = mode;
}
// 根据 mode 的值获取 random 实例
public Random getRandom() {
return mode == 0 ? rnd : tRnd.get();
}
// 打印并返回生成随机数的时间
@Override
public Long call() throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < GEN_COUNT; i++) {
getRandom().nextInt();
}
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " spend " + (end - start) + "ms");
return end - start;
}
}
- 定义主函数
public static void main(String[] args) throws ExecutionException, InterruptedException {
// **** 测试一:使用多线程共享的 Random 的实例 ****
Future<Long>[] future01 = new Future[GEN_COUNT];
// 生成随机数
for (int i = 0; i < THREAD_NUM; i++) {
future01[i] = pool.submit(new RndTask(0));
}
// 统计时间
long time01 = 0;
for (int i = 0; i < THREAD_NUM; i++) {
time01 += future01[i].get();
}
System.out.println("使用多线程共享的 Random 的实例: " + time01 + " ms");
// **** 测试二:使用 ThreadLocal 封装的 Random 实例 ****
Future<Long>[] future02 = new Future[GEN_COUNT];
// 生成随机数
for (int i = 0; i < THREAD_NUM; i++) {
future02[i] = pool.submit(new RndTask(1));
}
// 统计时间
long time02 = 0;
for (int i = 0; i < THREAD_NUM; i++) {
time02 += future02[i].get();
}
System.out.println("使用 ThreadLocal 封装的 Random 实例: " + time02 + " ms");
}
- 执行结果
pool-1-thread-3 spend 2860ms
pool-1-thread-4 spend 2896ms
pool-1-thread-2 spend 2896ms
pool-1-thread-1 spend 2900ms
使用多线程共享的 Random 的实例: 11552 ms
pool-1-thread-1 spend 119ms
pool-1-thread-4 spend 121ms
pool-1-thread-3 spend 122ms
pool-1-thread-2 spend 123ms
使用 ThreadLocal 封装的 Random 实例: 485 ms
从上面可以看出,使用 ThreadLocal 可以大大提高性能。