《Java并发性和多线程介绍》-Java TheadLocal

本文介绍了Java中的ThreadLocal类,解释了如何使用它来创建线程局部变量,包括创建ThreadLocal对象、访问ThreadLocal对象、ThreadLocal泛型使用、初始化ThreadLocal及完整示例。同时还提到了InheritableThreadLocal类,它是ThreadLocal的子类,允许子线程继承父线程的ThreadLocal变量。

Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有一个指向同一个ThreadLocal变量的引用,但是这两个线程依然不能看到彼此的ThreadLocal变量域。

1、创建一个ThreadLocal对象
2、访问ThreadLocal对象
3、ThreadLocal泛型
4、初始化ThreadLocal
5、Full ThreadLocal Example
6、InheritableThreadLocal

1、创建一个ThreadLocal对象

如下所示,创建一个ThreadLocal变量:

1 private ThreadLocal myThreadLocal = new ThreadLocal();

你实例化了一个ThreadLocal对象。每个线程仅需要实例化一次即可。虽然不同的线程执行同一段代码时,访问同一个ThreadLocal变量,但是每个线程只能看到私有的ThreadLocal实例。所以不同的线程在给ThreadLocal对象设置不同的值时,他们也不能看到彼此的修改。

2、访问ThreadLocal对象

一旦创建了一个ThreadLocal对象,你就可以通过以下方式来存储此对象的值:

1 myThreadLocal.set("A thread local value");

也可以直接读取一个ThreadLocal对象的值:

1 String threadLocalValue = (String) myThreadLocal.get();

get()方法会返回一个Object对象,而set()方法则依赖一个Object对象参数。

3、ThreadLocal泛型

为了使get()方法返回值不用做强制类型转换,通常可以创建一个泛型化的ThreadLocal对象。以下就是一个泛型化的ThreadLocal示例:

1 private ThreadLocal myThreadLocal1 = new ThreadLocal<String>();

现在你可以存储一个字符串到ThreadLocal实例里,此外,当你从此ThreadLocal实例中获取值的时候,就不必要做强制类型转换。

1 myThreadLocal1.set("Hello ThreadLocal");
2 String threadLocalValues = myThreadLocal.get();

4、初始化ThreadLocal

由于ThreadLocal对象的set()方法设置的值只对当前线程可见,那有什么方法可以为ThreadLocal对象设置的值对所有线程都可见。

为此,我们可以通过ThreadLocal子类的实现,并覆写initialValue()方法,就可以为ThreadLocal对象指定一个初始化值。如下所示:

1 private ThreadLocal myThreadLocal = new ThreadLocal<String>() {
2    @Override protected String initialValue() {
3        return "This is the initial value";
4    }
5 };

此时,在set()方法调用前,当调用get()方法的时候,所有线程都可以看到同一个初始化值。

5、Full ThreadLocal Example

以下是一个完整的ThreadLocal示例:

01 public class ThreadLocalExample {
02  
03     public static class MyRunnable implements Runnable {
04  
05         private ThreadLocal<Integer> threadLocal =
06                new ThreadLocal<Integer>();
07  
08         @Override
09         public void run() {
10             threadLocal.set( (int) (Math.random() * 100D) );
11  
12             try {
13                 Thread.sleep(2000);
14             catch (InterruptedException e) {
15             }
16  
17             System.out.println(threadLocal.get());
18         }
19     }
20  
21     public static void main(String[] args) {
22         MyRunnable sharedRunnableInstance = new MyRunnable();
23  
24         Thread thread1 = new Thread(sharedRunnableInstance);
25         Thread thread2 = new Thread(sharedRunnableInstance);
26  
27         thread1.start();
28         thread2.start();
29  
30         thread1.join(); //wait for thread 1 to terminate
31         thread2.join(); //wait for thread 2 to terminate
32     }
33  
34 }

上面创建了两个线程共享一个MyRunnable实例。每个线程执行run()方法的时候,会给同一个ThreadLocal实例设置不同的值。如果调用set()方法的时候用synchronized关键字同步,而且不是一个ThreadLocal对象实例,那么第二个线程将会覆盖第一个线程所设置的值。

然而,由于是ThreadLocal对象,所以两个线程无法看到彼此的值。因此,可以设置或者获取不同的值。

6、InheritableThreadLocal

InheritableThreadLocal类是ThreadLocal的子类。为了解决ThreadLocal实例内部每个线程都只能看到自己的私有值,所以InheritableThreadLocal允许一个线程创建的所有子线程访问其父线程的值。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 《Java并发性和多线程介绍》-Java TheadLocal

<think>我们正在讨论Java中的ThreadLocal类,它用于实现线程本地存储。每个线程都有自己独立的变量副本,因此不会出现线程共享问题。 根据引用,我们可以总结ThreadLocal的使用要点: 1. 通常使用private static修饰ThreadLocal实例。 2. 通过set(T value)方法设置当前线程的副本值。 3. 通过get()方法获取当前线程的副本值。 4. 使用完毕后,最好调用remove()方法清除当前线程的副本值,以避免内存泄漏(特别是在线程池中,线程是复用的)。 下面是一个简单的使用示例,展示如何为每个线程存储一个整数: ```java public class ThreadLocalExample { // 创建一个ThreadLocal变量,用于存储整数 private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { // 创建并启动多个线程 for (int i = 0; i < 5; i++) { new Thread(() -> { // 设置当前线程的初始值(这里设置为一个随机整数) int value = (int) (Math.random() * 100); threadLocal.set(value); System.out.println(Thread.currentThread().getName() + " 设置的值: " + value); try { // 模拟执行其他操作 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 获取当前线程存储的值 System.out.println(Thread.currentThread().getName() + " 获取的值: " + threadLocal.get()); // 使用完毕后,移除当前线程存储的值,防止内存泄漏 threadLocal.remove(); }).start(); } } } ``` 在这个例子中,每个线程都设置一个随机整数到自己的ThreadLocal副本中,然后稍后获取它。注意,每个线程只能获取到自己设置的值,不会受到其他线程的影响。 另外,根据引用[3][4],我们还可以将ThreadLocal封装在一个工具类中,以便于使用: ```java public class MyThreadLocal { // 使用private static修饰 private static ThreadLocal<Context> contextThreadLocal = new ThreadLocal<>(); public static void set(Context context) { contextThreadLocal.set(context); } public static Context get() { return contextThreadLocal.get(); } public static void remove() { contextThreadLocal.remove(); } } ``` 这样,在程序的任何地方,都可以通过MyThreadLocal类来设置获取当前线程的Context对象。 需要注意的是,ThreadLocalMap中每个条目(Entry)是弱引用(WeakReference)到ThreadLocal对象(见引用[5])。但是,如果ThreadLocal对象被设置为null,而线程仍然存活(比如线程池中的线程),那么由于线程的ThreadLocalMap还持有Entry,而Entry的value是强引用,所以这个value不会被回收,从而可能导致内存泄漏。因此,使用完ThreadLocal后,最好调用remove()方法清除。 总结ThreadLocal的使用步骤: 1. 创建ThreadLocal对象(通常为private static)。 2. 在需要的时候,通过set方法设置当前线程的变量副本。 3. 在需要的地方,通过get方法获取当前线程的变量副本。 4. 最后,调用remove方法清理当前线程的变量副本(特别是线程复用的情况下)。 希望这个教程示例能帮助你理解ThreadLocal的使用方法。 §§相关问题§§ 1. ThreadLocal为什么会导致内存泄漏? 2. ThreadLocal在Spring框架中有哪些应用? 3. 如何避免ThreadLocal的内存泄漏问题? 4. ThreadLocalSynchronized有什么区别?</think>### Java ThreadLocal 使用教程与示例 `ThreadLocal` 是 Java 中实现线程本地存储的关键类,它为每个线程提供独立的变量副本,解决多线程资源共享问题。以下是详细使用方法: #### 一、基本使用步骤 1. **创建 ThreadLocal 对象** 通常使用 `private static final` 修饰: ```java private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); ``` 2. **设置线程局部变量** 通过 `set()` 方法为当前线程设置值: ```java threadLocal.set(100); // 当前线程独享此值 ``` 3. **获取线程局部变量** 通过 `get()` 方法获取当前线程的值: ```java int value = threadLocal.get(); // 其他线程无法访问此值 ``` 4. **清理资源** 使用后必须调用 `remove()` 防止内存泄漏: ```java threadLocal.remove(); // 重要!尤其在线程池环境 ``` #### 二、完整示例代码 ```java public class ThreadLocalDemo { // 1. 创建ThreadLocal实例 private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { // 创建3个线程 for (int i = 0; i < 3; i++) { new Thread(() -> { // 2. 设置线程专属值(当前线程ID) int threadId = (int) Thread.currentThread().getId(); threadLocal.set(threadId); // 3. 模拟业务操作 System.out.println("线程[" + Thread.currentThread().getName() + "] 设置值: " + threadId); // 4. 获取并验证数据隔离性 System.out.println("线程[" + Thread.currentThread().getName() + "] 读取值: " + threadLocal.get()); // 5. 必须清理!防止内存泄漏 threadLocal.remove(); }).start(); } } } ``` #### 三、关键特性说明 1. **线程隔离性** 每个线程操作自己的副本,互不干扰(对比 `synchronized` 的锁竞争)[^4]。 2. **内存管理** - 底层使用 `ThreadLocalMap` 存储数据(弱引用键+强引用值)[^5] - **必须调用 `remove()`**:尤其在线程池中,线程复用会导致旧值残留 3. **典型应用场景** - 用户会话信息(如 Spring 的 `RequestContextHolder`) - 数据库连接管理(如 Hibernate 的 `Session` 管理) - 避免方法参数透传 #### 四、封装工具类示例 ```java public class UserContextHolder { // 封装ThreadLocal容器 private static final ThreadLocal<User> userHolder = new ThreadLocal<>(); public static void setUser(User user) { userHolder.set(user); } public static User getUser() { return userHolder.get(); } public static void clear() { userHolder.remove(); // 强制清理 } } // 使用示例 UserContextHolder.setUser(currentUser); // 请求开始时设置 User currentUser = UserContextHolder.getUser(); // 任意位置获取 UserContextHolder.clear(); // 请求结束时清理 ``` > **重要提示**:在高并发场景中,必须通过 `try-finally` 确保 `remove()` 被调用: > ```java > try { > threadLocal.set(data); > // ...业务逻辑 > } finally { > threadLocal.remove(); // 绝对执行清理 > } > ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值