Java ThreadLocal原理和用法
Java类使您能够创建只能由同一线程读取和写入的变量。因此,即使两个线程正在执行相同的代码,并且该代码引用了相同的变量 ThreadLocal,这两个线程也无法看到彼此的ThreadLocal变量。因此,Java ThreadLocal 类提供了一种简单的方法来使代码线程安全,否则就不会如此。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
1. ThreadLocal简介
Java ThreadLocal
类使您能够创建只能由同一线程读取和写入的变量。因此,即使两个线程正在执行相同的代码,并且该代码引用了相同的变量 ThreadLocal,这两个线程也无法看到彼此的ThreadLocal变量。因此,Java ThreadLocal 类提供了一种简单的方法来使代码 线程安全,否则就不会如此。
2. ThreadLocal使用场景
ThreadLocal在我们实际开发过程中用的相当之多,喜欢看spring源码的朋友应该知道,在我们使用较多的spring框架中,ThreadLocal随处可见,以下是ThreadLocal一些常见的使用场景:
2.1 数据库连接管理
在多线程环境下管理数据库连接,每个线程持有自己的数据库连接。
2.2 会话管理
在 Web 应用中管理用户会话,每个线程可以访问自己的会话信息。
2.3 事务管理
在事务管理中,每个线程可以有自己的事务,并且可以确保线程安全。
2.4 缓存
在多线程环境下,可以使用 ThreadLocal 来管理线程安全的缓存。
2.5 单实例的多线程调用
某个类在多线程环境下只能有一个实例,但是每个线程都可以访问这个实例,并且保证线程安全。
3. 创建 ThreadLocal
创建ThreadLocal
实例的方式与创建其他 Java 对象的方式相同 - 通过new 运算符。下面是一个示例,演示如何创建ThreadLocal
变量:
private ThreadLocal threadLocal = new ThreadLocal();
每个线程只需执行一次。现在多个线程可以获取和设置此 内的值 ThreadLocal,并且每个线程只能看到它自己设置的值。
4. 设置 ThreadLocal 值
一旦ThreadLocal创建了,您就可以使用它的方法设置要存储在其中的值 set()。
threadLocal.set("一个线程本地值");
5. 获取 ThreadLocal 值
ThreadLocal你可以使用它的方法 读取存储在 中的值get()。下面是一个获取存储在 Java 中的值的示例ThreadLocal:
String threadLocalValue = (String) threadLocal.get();
6. 删除 ThreadLocal 值
要删除 ThreadLocal
变量中设置的值。您可以通过调用 ThreadLocal remove()
方法来删除值。以下是删除 Java 上设置的值的示例ThreadLocal:
threadLocal.remove();
7. 泛型ThreadLocal
您可以使用ThreadLocal泛型类型创建 。使用泛型类型,只有泛型类型的对象才能设置为上的值ThreadLocal。此外,您不必对 返回的值进行类型转换 get()。以下是泛型ThreadLocal示例:
private ThreadLocal<String> myThreadLocal = new ThreadLocal<String>();
现在您只能在ThreadLocal
实例中存储字符串。此外,您不需要对从以下代码获取的值进行类型转换ThreadLocal
:
myThreadLocal.set("Hello ThreadLocal");
String threadLocalValue = myThreadLocal.get();
8. 初始 ThreadLocal 值
可以为 Java 设置一个初始值ThreadLocal
,该值将在第一次 get()调用时使用 - 在set()使用新值调用之前。您有两种方式可以为 ThreadLocal
指定初始值:
- 创建一个覆盖该方法的 ThreadLocal 子类initialValue()。
- 创建一个具有接口实现的 ThreadLocal Supplier。
我将在以下部分向您展示这两个选项。
8.1 覆盖 initialValue()
为Java ThreadLocal变量指定初始值的第一种方法是创建ThreadLocal的子类,该子类覆盖其initialValue()方法。创建ThreadLocal子类的最简单方法是在创建ThreadLocal变量的位置创建一个匿名子类。以下是一个创建ThreadLocal匿名子类的示例,该子类覆盖了initialValue()方法:
private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
@Override protected String initialValue() {
return String.valueOf(System.currentTimeMillis());
}
};
请注意,不同的线程仍然会看到不同的初始值。每个线程都将创建自己的初始值。只有当你从initialValue()方法返回完全相同的对象时,所有线程才会看到相同的对象。然而,首先使用ThreadLocal的重点是避免不同的线程看到相同的实例。
8.2 提供一个Supplier实现
为Java ThreadLocal变量指定初始值的第二种方法是使用其静态工厂方法,initial(Supplier)将Supplier接口实现作为参数传递。此供应商实现为ThreadLocal提供初始值。下面是一个使用其withInitial()静态工厂方法创建ThreadLocal的示例,将一个简单的Supplier实现作为参数传递:
ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
return String.valueOf(System.currentTimeMillis());
}
});
由于Supplier是一个函数接口,因此可以使用Java Lambda表达式来实现。以下是将Supplier实现作为lambda表达式提供给withInitial()的样子:
ThreadLocal threadLocal = ThreadLocal.withInitial(
() -> { return String.valueOf(System.currentTimeMillis()); } );
正如你所看到的,这比前面的例子要短一些。但是,使用最密集的lambda表达式语法,它甚至可以做得更短一些:
ThreadLocal threadLocal3 = ThreadLocal.withInitial(
() -> String.valueOf(System.currentTimeMillis()) );
9. ThreadLocal 值的延迟设置
在某些情况下,您不能使用标准方式设置初始值。例如,您可能需要一些在创建 ThreadLocal 变量时不可用的配置信息。在这种情况下,您可以延迟设置初始值。以下是如何在 Java ThreadLocal 上延迟设置初始值的示例:
public class MyDateFormatter {
private ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<>();
public String format(Date date) {
SimpleDateFormat simpleDateFormat = getThreadLocalSimpleDateFormat();
return simpleDateFormat.format(date);
}
private SimpleDateFormat getThreadLocalSimpleDateFormat() {
SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
if(simpleDateFormat == null) {
simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
simpleDateFormatThreadLocal.set(simpleDateFormat);
}
return simpleDateFormat;
}
}
请注意format()方法如何调用getThreadLocalSimpleDateFormat()
方法来获取Java SimpleDatFormat实例。如果尚未在ThreadLocal中设置SimpleDateFormat实例,则会在ThreadLocal变量中创建并设置一个新的SimpleDateFormat。一旦线程在ThreadLocal变量中设置了自己的SimpleDateFormat,该线程将继续使用相同的SimpleDateFormat对象。但仅限于这条线。每个线程都创建自己的SimpleDateFormat实例,因为它们看不到ThreadLocal变量上设置的其他实例。
SimpleDateFormat类不是线程安全的,因此多个线程不能同时使用它。为了解决这个问题,上面的MyDateFormatter类为每个线程创建了一个SimpleDateFormat,因此每个调用format()方法的线程都将使用自己的SimpleDateFormat实例。
10. 将 ThreadLocal 与线程池或 ExecutorService 结合使用
如果您计划在传递给Java线程池或Java ExecutorService
的任务中使用Java ThreadLocal,请记住,您无法保证哪个线程将执行您的任务。然而,如果你只需要确保每个线程都使用它自己的某个对象的实例,这不是问题。然后,您可以将Java ThreadLocal与线程池或ExecutorService一起使用。
这是一个完全可运行的 JavaThreadLocal示例:
public class ThreadLocalExample {
public static void main(String[] args) {
MyRunnable sharedRunnableInstance = new MyRunnable();
Thread thread1 = new Thread(sharedRunnableInstance);
Thread thread2 = new Thread(sharedRunnableInstance);
thread1.start();
thread2.start();
thread1.join(); //等待线程 1 终止
thread2.join(); //等待线程 2 终止
}
}
public class MyRunnable implements Runnable {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
@Override
public void run() {
threadLocal.set( (int) (Math.random() * 100D) );
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(threadLocal.get());
}
}
此示例创建了一个MyRunnable实例,并将其传递给两个不同的线程。两个线程都执行run()方法,因此在ThreadLocal实例上设置了不同的值。如果对set()调用的访问已经同步,并且它不是ThreadLocal对象,则第二个线程将覆盖第一个线程设置的值。
但是,由于它是ThreadLocal对象,因此两个线程无法看到彼此的值。因此,它们设置并获得不同的值。
11. 可继承的ThreadLocal(InheritableThreadLocal)
InheritableThreadLocal
类是ThreadLocal的一个子类。与每个线程在ThreadLocal中都有自己的值不同,InheritableThreadLocal
授予线程和该线程创建的所有子线程对值的访问权限。以下是一个完整的Java Inheritable ThreadLocal示例:
public class InheritableThreadLocalBasicExample {
public static void main(String[] args) {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
InheritableThreadLocal<String> inheritableThreadLocal =
new InheritableThreadLocal<>();
Thread thread1 = new Thread(() -> {
System.out.println("===== Thread 1 =====");
threadLocal.set("Thread 1 - ThreadLocal");
inheritableThreadLocal.set("Thread 1 - InheritableThreadLocal");
System.out.println(threadLocal.get());
System.out.println(inheritableThreadLocal.get());
Thread childThread = new Thread( () -> {
System.out.println("===== ChildThread =====");
System.out.println(threadLocal.get());
System.out.println(inheritableThreadLocal.get());
});
childThread.start();
});
thread1.start();
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("===== Thread2 =====");
System.out.println(threadLocal.get());
System.out.println(inheritableThreadLocal.get());
});
thread2.start();
}
}
此示例创建了一个普通的Java ThreadLocal和一个Java InheritableThreadLocal。然后,该示例创建了一个线程,该线程设置了ThreadLocal和InheritableThreadLocal的值,然后创建了一个子线程,该子线程访问ThreadLocal和InheritableThreadLocal值。子线程只能看到InheritableThreadLocal的值。
最后,该示例创建了第三个线程,该线程也尝试访问ThreadLocal和InheritableThreadLocal,但看不到第一个线程存储的任何值。
运行此示例打印的输出如下:
===== Thread 1 =====
Thread 1 - ThreadLocal
Thread 1 - InheritableThreadLocal
===== ChildThread =====
null
Thread 1 - InheritableThreadLocal
===== Thread2 =====
null
null
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)