目录
1、Threadlocal简介
2、ThreadLocal的主要方法:
2.1 initialValue():初始化ThreadLocal变量的值
2.2 set():为当前线程设置ThreadLocal变量的值
2.3 get():获取当前线程中ThreadLocal变量的值
2.4 remove():移除当前线程中ThreadLocal变量的值
3、ThreadLocal的优点
4、ThreadLocal的缺点
5、ThreadLocal常见应用场景
6、使用ThreadLocal对象
小结
ThreadLocal是一个Java中的线程本地变量,它提供了一种在多线程环境下保证每个线程拥有自己独立的变量副本的方式。在多线程编程中,使用ThreadLocal可以避免线程安全问题,提高程序的性能和可靠性。
当多线程访问共享且可变的数据时,涉及到多线程间同步的问题,并不是所有时候,都要用到共享数据。使用ThreadLocal ,这样无需synchronized或ConcurrentHashMap,可以在不影响性能的情况下,也无需层层传递参数,达到保存业务内容信息的目的。
Threadlocal的结构:
当调用get()方法获取ThreadLocal变量时,如果该变量还没有被设置过值,则会自动调用initialValue()方法来获取初始值。
initialValue()方法是一个protected方法,需要在ThreadLocal的子类中进行重写。一般情况下,我们可以使用匿名内部类来重写initialValue()方法,如下所示:
ThreadLocal threadLocal = new ThreadLocal() {
@Override
protected String initialValue() {
return "default";
}
};
在上述代码中,我们创建了一个ThreadLocal对象,并重写了它的initialValue()方法,使其返回一个默认值"default"。当调用threadLocal.get()方法时,如果该ThreadLocal变量还没有被设置过值,则会返回默认值"default"。
需要注意的是,initialValue()方法只会在第一次调用get()方法时被调用,之后再调用get()方法时不会再次调用initialValue()方法。如果需要重新设置ThreadLocal变量的值,可以调用set()方法或者remove()方法。
它可以用来初始化ThreadLocal变量的值,避免出现空指针异常等问题。在使用ThreadLocal时,应该根据实际需要来重写initialValue()方法,以便为ThreadLocal变量设置合适的默认值。
当调用set()方法设置ThreadLocal变量的值时,该值会被存储到当前线程的ThreadLocalMap中,并与当前ThreadLocal对象关联起来。set()方法的使用非常简单,只需要调用ThreadLocal对象的set()方法,并传入要设置的值即可,如下所示:
ThreadLocal threadLocal = new ThreadLocal<>();
threadLocal.set("value");
在上述代码中,我们创建了一个ThreadLocal对象,并调用它的set()方法,将字符串"value"设置为当前线程的ThreadLocal变量的值。当调用get()方法获取ThreadLocal变量的值时,就可以获取到该值了。
需要注意的是,在同一个线程中多次调用set()方法设置ThreadLocal变量的值,只会保留最后一次设置的值。另外,如果ThreadLocal变量的值与当前线程已经存在的值相同,则不会对ThreadLocal变量的值进行更新,而是直接返回原有的值。这也意味着,如果需要更新ThreadLocal变量的值,必须显式地调用set()方法。
它可以用于为当前线程设置ThreadLocal变量的值。在使用set()方法时,需要注意线程隔离的特性,以及多次设置ThreadLocal变量值时只保留最后一次设置的值的特点。
当调用get()方法获取ThreadLocal变量的值时,会从当前线程的ThreadLocalMap中查找与当前ThreadLocal对象关联的变量值,并返回该值。用法如下:
ThreadLocal threadLocal = new ThreadLocal<>();
threadLocal.set("value");
String value = threadLocal.get();
需要注意的是,每个线程都有自己独立的ThreadLocal变量副本,因此不同线程之间获取的ThreadLocal变量的值互不干扰。而且,在同一个线程中多次调用get()方法获取ThreadLocal变量的值,会返回同一个值。另外,如果当前线程没有设置ThreadLocal变量的值,则会返回null。如果需要设置ThreadLocal变量的默认值,可以通过重写ThreadLocal的initialValue()方法来实现。
在使用get()方法时,需要注意线程隔离的特性,以及获取ThreadLocal变量的默认值的问题。
当调用remove()方法移除ThreadLocal变量的值时,会从当前线程的ThreadLocalMap中移除与当前ThreadLocal对象关联的变量值。
在同一个线程中多次调用remove()方法移除ThreadLocal变量的值,只会移除最后一次设置的值。另外,如果当前线程没有设置ThreadLocal变量的值,或者已经调用过remove()方法移除了变量的值,则再次调用remove()方法不会产生任何效果。
ThreadLocal的原理图:
图片来源:https://baijiahao.baidu.com/s?id=1773187827419181532&wfr=spider&for=pc
Thread,ThreadLocal 以及 ThreadLocalMap三者关系图示:
图片来源:ThreadLocal 详解 - 知乎
(1)线程隔离:每个线程都有自己独立的变量副本,互不干扰,保证线程安全;
(2)高效性:相比于使用synchronized,ThreadLocal不需要加锁,能够提供更高的并发性能,有助于提高执行效率;
(3)简单易用:ThreadLocal的使用相对简单,只需创建一个ThreadLocal对象,并通过get()和set()方法进行读写操作;
(4)传参便利:在一些场景中,需要将某些数据在多个方法之间传递,但不希望通过方法参数传递或者全局变量来实现。ThreadLocal可以作为一种简洁的方式来传递上下文信息。
(1)内存泄漏风险:由于ThreadLocal的特性,每个线程持有一个变量副本,如果没有及时清理,可能会导致内存泄漏。在使用完ThreadLocal后,应该调用remove()方法来清除线程的变量副本;
(2)上下文切换问题:在使用ThreadLocal时,如果频繁地在线程间切换,可能会导致ThreadLocal中的数据无法被及时清理,从而影响程序的性能和内存消耗;
(3)难以调试:由于每个线程都有自己的变量副本,当出现问题时,调试起来可能会比较困难。需要仔细分析每个线程的状态和变量副本的值;
图片来源:https://baijiahao.baidu.com/s?id=1773187827419181532&wfr=spider&for=pc
使用ThreadLocal对象存放的变量能够达到线程安全的目的,它的一般用法如下:
public final static ThreadLocal
PARAMS = new ThreadLocal ();
下面是相关代码示例:
public class ThreadLocalExample {
//可以将ThreadLocal对象声明为一个全局常量,所有的线程均使用这一常量
//public final static ThreadLocal threadLocal = new ThreadLocal()
private static ThreadLocal threadLocal = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) throws InterruptedException {
// 创建多个线程并启动
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new CounterRunnable());
thread.start();
}
// 主线程等待所有子线程结束
Thread.sleep(1000);
// 输出结果
System.out.println("Final result: " + threadLocal.get());
}
static class CounterRunnable implements Runnable {
@Override
public void run() {
// 获取当前线程的变量副本,并进行自增操作
int count = threadLocal.get();
for (int i = 0; i < 10000; i++) {
count++;
}
// 将修改后的值保存回变量副本中
threadLocal.set(count);
// 输出结果
System.out.println("Thread " + Thread.currentThread().getId() + " finished. Count: " + count);
}
}
}
在上述代码中,我们创建了一个静态的ThreadLocal对象threadLocal,并重写了它的initialValue()方法,使其初始值为0。在主线程中创建多个子线程,并启动它们执行CounterRunnable任务。每个子线程在执行run()方法时,先通过threadLocal.get()获取当前线程的变量副本,进行自增操作,然后再通过threadLocal.set(count)将修改后的值保存回变量副本中。
由于使用了ThreadLocal对象,每个线程都拥有自己独立的变量副本,并且不会相互干扰,从而避免了线程安全问题。在所有子线程执行完毕后,我们可以通过threadLocal.get()方法获取主线程的变量副本,得到最终的结果。
需要注意的是,在使用ThreadLocal时,一定要在每个线程结束时调用remove()方法来清除线程的变量副本,以防止出现内存泄漏的情况。
总而言之,ThreadLocal对象是一种非常实用的多线程编程工具,可以有效地避免线程安全问题,提高程序的性能和可靠性。它的使用场景包括但不限于线程池、Web应用程序等。
参考:
https://baijiahao.baidu.com/s?id=1773187827419181532&wfr=spider&for=pc
ThreadLocal 详解 - 知乎
ThreadLocal 使用手册 | 建议收藏 - 简书
Java 超详细讲解ThreadLocal类的使用_java_脚本之家
感谢阅读,码字不易,多谢点赞!如有不当之处,欢迎反馈指出,感谢!