在处理多线程并发安全的方法中,最常用的方法,就是使用锁,通过锁来控制多个不同线程对临界区的访问。但是,无论是什么样的锁,乐观锁或者悲观锁,都会在并发冲突的时候对性能产生一定的影响。那有没有一种方法,可以彻底避免竞争呢?
答案是肯定的,这就是ThreadLocal。
ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量。
如何创建
1.直接new
private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
2.创建并初始化
private static final ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
/**
* ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
*/
@Override
protected String initialValue(){
System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
return "这里是initialValue方法设置的默认值";
}
};
使用
1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
private static final ThreadLocal<String> threadLocal = new ThreadLocal<String>(){
/**
* ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
*/
@Override
protected String initialValue(){
System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
return "这里是initialValue方法设置的默认值";
}
};
public static void main(String[] args) {
System.out.println("线程" + Thread.currentThread().getId() + ":" + TestCtrl.threadLocal.get());
TestCtrl.threadLocal.set("1111");
System.out.println("线程" + Thread.currentThread().getId() + ":" + TestCtrl.threadLocal.get());
new Thread(new MyIntegerTask()).start();
}
public static class MyIntegerTask implements Runnable{
@Override
public void run() {
System.out.println("线程" + Thread.currentThread().getId() + ":" + TestCtrl.threadLocal.get());
TestCtrl.threadLocal.set("222");
System.out.println("线程" + Thread.currentThread().getId() + ":" + TestCtrl.threadLocal.get());
}
}
执行结果
将 ThreadLocal 定义为 private static 是一种常见的做法,可以确保该 ThreadLocal 实例在整个应用程序或类的生命周期内是唯一的。这样做可以避免在多个地方创建多个 ThreadLocal 实例,确保线程间共享同一个 ThreadLocal 对象。
public class SharedThreadLocalExample {
private static ThreadLocal<String> sharedThreadLocal = new ThreadLocal<>();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
sharedThreadLocal.set("Value from Thread 1");
System.out.println("Thread 1: " + sharedThreadLocal.get());
});
Thread thread2 = new Thread(() -> {
sharedThreadLocal.set("Value from Thread 2");
System.out.println("Thread 2: " + sharedThreadLocal.get());
});
thread1.start();
thread2.start();
}
}
在上述示例中,我们定义了一个 private static 的 ThreadLocal 实例 sharedThreadLocal。然后,我们创建了两个线程 thread1 和 thread2,并在每个线程中使用共享的 sharedThreadLocal 实例。在每个线程中,我们使用 set() 方法设置线程局部变量的值,然后使用 get() 方法获取该值,并打印输出。
运行上述代码,您将看到两个线程分别设置并获取了相应的线程局部变量的值。由于 sharedThreadLocal 是 private static,多个线程共享同一个实例,并且可以独立地设置和获取各自的线程局部变量的值。
请注意,尽管多个线程可以共享同一个 ThreadLocal 实例,但线程之间的线程局部变量是相互隔离的,每个线程对 ThreadLocal 的修改不会影响其他线程。这是 ThreadLocal 提供的线程隔离的特性。
ThreadLocal中的内存泄露
先简单看一下ThreadLocal的基本原理
通过观看ThreadLocal.get方法的源码我们可以看到,所谓的ThreadLocal变量就是保存在每个线程的map中的
ThreadLocalMap是一个比较特殊的Map,它的每个Entry的key都是一个弱引用。(当一个对象只被弱引用引用时,如果垃圾回收器发现该对象只有弱引用指向它,那么在下一次垃圾回收时,就会回收该对象)
这样设计的好处是,如果这个变量不再被其他对象使用时,可以自动回收这个ThreadLocal对象,避免可能的内存泄露
虽然ThreadLocalMap中的key是弱引用,当不存在外部强引用的时候,就会自动被回收,但是Entry中的value依然是强引用。即使线程退出,如果我们没有显示的调用remove方法,则启依然会存在内存中,而由于其是强引用,垃圾回收器又回收不了,则有可能发生内存泄露。
因此,我们可以通过以下方法避免ThreadLocal 内存泄露
- 及时清理:在使用完 ThreadLocal 后,应该调用 remove() 方法清理其中的数据。可以在使用 ThreadLocal 的代码块结束时,或者在线程结束时执行清理操作。
- 使用 try-finally:在某些情况下,当使用 ThreadLocal 存储的数据需要确保在代码块结束时被清理时,可以使用 try-finally 块来确保清理操作一定会执行。
- 使用弱引用:在一些特定场景下,可以使用弱引用(WeakReference)来存储 ThreadLocal 变量的值,使其在不被使用时可以被垃圾回收。
总结起来,为了避免 ThreadLocal 导致的内存泄漏问题,需要及时清理 ThreadLocal 中的数据,并确保在适当的时机调用 remove() 方法。正确地管理 ThreadLocal 的生命周期可以避免不必要的内存占用和潜在的内存泄漏风险。
父子线程间可传递 InheritableThreadLocal
InheritableThreadLocal 是 ThreadLocal 的一个子类,它提供了一种在子线程中继承父线程中的线程局部变量的机制。当使用 InheritableThreadLocal 存储数据时,子线程可以访问并使用父线程中设置的值。
通常情况下,ThreadLocal 中存储的值只能在创建它的线程中访问和使用。但是,当使用 InheritableThreadLocal 时,子线程可以继承父线程中的 ThreadLocal 值,并在自己的执行过程中使用这些值。这对于一些需要在多个线程之间传递上下文信息的场景非常有用。
以下是对 InheritableThreadLocal 的一些要点进行解释:
- 继承性:InheritableThreadLocal 允许子线程继承父线程的 ThreadLocal 值。在创建子线程时,子线程会自动拷贝父线程中的 ThreadLocal 值,使得子线程具有相同的 ThreadLocal 值副本。
- 父子线程关系:InheritableThreadLocal 并不是线程之间共享数据,而是通过继承关系传递数据。父线程设置的值只能在其创建的子线程中访问,而子线程无法访问其他父线程或同级子线程的 ThreadLocal 值。
- 用注意事项:尽管 InheritableThreadLocal 提供了线程间值的传递机制,但仍需要注意数据一致性和资源管理。当使用 InheritableThreadLocal 时,要确保正确设置和清理 ThreadLocal 值,避免数据泄漏和潜在的问题。
public class InheritableThreadLocalExample {
private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
inheritableThreadLocal.set("Parent Thread Value");
Thread parentThread = new Thread(() -> {
System.out.println("Parent Thread: " + inheritableThreadLocal.get());
Thread childThread = new Thread(() -> {
System.out.println("Child Thread: " + inheritableThreadLocal.get());
inheritableThreadLocal.set("childThread 修改");
Thread childThread1 = new Thread(() -> {
System.out.println("Child Thread1: " + inheritableThreadLocal.get());
});
childThread1.start();
});
childThread.start();
});
parentThread.start();
Thread.sleep(2000L);
System.out.println("main:" + inheritableThreadLocal.get());
}
}
运行结果如下