Java ThreadLocal使用浅析

本文介绍了 Java 中 ThreadLocal 类的基本概念及其应用场景。通过对比不同线程间共享与隔离变量的实例,展示了 ThreadLocal 如何避免多线程环境下的数据安全问题。

 

Java ThreadLocal使用浅析

 

 

JAVA API 文档里关于 ThreadLocal 的定义是:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

这个类提供了一个线程本地的变量。这些变量在被共享访问的情况下在不同的线程里是独立的 ( 必须通过 get set 方法来访问 )

 

很显然该类提供了一个机制可以防止多线程访问带来的不安全机制。实际上就是在线程本地保存一个变量,而不是通过共享变量。这个就要看我们的使用场合了,如果我们确实需要共享的数据,那还是必须通过同步机制来保证数据的安全。如果有些情况希望不同的线程保存的变量各自分开,那用这个还是比较合适的。

ThreadLocal 这个类本身不是代表线程要访问的变量,这个类的成员变量才是。 JDK1.5 ThreadLocal 加了泛型功能,即是 ThreadLocal<T>, 这个泛型 T 即是要线程的本地变量。线程通过 ThreadLocal get set 方法去访问这个变量 T ThreadLocal 提供了一个机制,它能保证线程通过这个来访问它来访问类型为 T 的变量的时候是不同的拷贝。所以访问该变量必须通过 Threadlocal 这个类只提供了两个 public 方法,即是 get() set ()方法来访问。

同时还提供了一个 inintValue() protected 方法。该方法用来初始化变量值。

注意默认情况下 initValue(), 返回 null 。线程在没有调用 set 之前,第一次调用 get 的时候, get 方法会默认去调用 initValue 这个方法。所以如果没有覆写这个方法,可能导致 get 返回的是 null 。当然如果调用过 set 就不会有这种情况了。但是往往在多线程情况下我们不能保证每个线程的在调用 get 之前都调用了 set ,所以最好对 initValue 进行覆写,以免导致空指针异常。

说了这么多,估计还不如看下例子吧,下面还是上例子。

例子 1 :没有使用 Threadlocal 的情况:

/**

*

* @author yblin

* 2009.8.18

*

*/

public class TestWithNoThreadLocal {

public static int a = 0;


public static void main(String[] args) {

MyThread myThread = new MyThread();

myThread.start();

for ( int i = 0; i < 5; i++) {

a = a + 1;

System. out .println(Thread. currentThread ().getName() + ":" + a );

}

}

public static class MyThread extends Thread {

public void run() {

for ( int i = 0; i < 5; i++) {

a = a + 1;

System. out .println(Thread. currentThread ().getName() + ":" + a );

}

}

}


}


运行的一种结果如下:

main:2

Thread-0:2

main:3

Thread-0:4

main:5

Thread-0:6

main:7

Thread-0:8

main:9

Thread-0:10


在没有同步的机制下,该结果有不安全的情况发生是正常的,两个线程都得到 2 。下面请看用了 ThreadLocal 的效果。


例子 2 :使用了 Threadlocal 的情况:

/**

*

* @author yblin

* 2009.8.18

*

*/

public class TestThreadLocal2 {

private static ThreadLocal<Integer> a = new ThreadLocal<Integer>() {

public Integer initialValue() { // 初始化,默认是返回 null

return 0;

}

};


public static void main(String args[]) {

MyThread my;

my = new MyThread();

my.start();

for ( int i = 0; i < 5; i++) {

a .set( a .get() + 1);

System. out .println(Thread. currentThread ().getName() + ":" + a .get());

}

}


public static class MyThread extends Thread {

public void run() {

for ( int i = 0; i < 5; i++) {

a .set( a .get() + 1);

System. out .println(Thread. currentThread ().getName() + ":"

+ a .get());

}

}

}

}


运行的一种结果如下:

main:1

Thread-0:1

main:2

Thread-0:2

main:3

Thread-0:3

main:4

Thread-0:4

Thread-0:5

main:5


如上是关于 ThreadLocal 的简单介绍。其内部具体实现可看其类实现。

 

### 创建ThreadLocal变量 可以使用`ThreadLocal`类的默认构造函数来创建一个`ThreadLocal`变量。以下是示例代码: ```java ThreadLocal<String> threadLocalVariable = new ThreadLocal<>(); ``` 也可以使用`withInitial`方法为`ThreadLocal`变量提供一个初始值,此方法接收一个`Supplier`函数式接口,示例如下: ```java ThreadLocal<String> threadLocalWithInitial = ThreadLocal.withInitial(() -> "Initial Value"); ``` ### 设置和获取值 - **设置值**:使用`set`方法将值存储到`ThreadLocal`变量中,它会为当前线程关联这个值。 ```java threadLocalVariable.set("Value for current thread"); ``` - **获取值**:使用`get`方法从`ThreadLocal`变量中获取当前线程关联的值。若未设置值,对于使用默认构造函数创建的`ThreadLocal`会返回`null`;对于使用`withInitial`方法创建的,会返回初始值。 ```java String value = threadLocalVariable.get(); ``` ### 移除值 当不再需要`ThreadLocal`中存储的值时,为避免潜在的内存泄漏问题,可以使用`remove`方法来移除当前线程关联的值。 ```java threadLocalVariable.remove(); ``` ### 完整示例 以下是一个完整的示例,展示了多个线程使用`ThreadLocal`的情况: ```java public class ThreadLocalExample { // 创建一个ThreadLocal变量 private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { // 创建并启动线程1 Thread thread1 = new Thread(() -> { // 设置线程1的ThreadLocalthreadLocal.set("Value for Thread 1"); // 获取并打印线程1的ThreadLocal值 System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 移除线程1的ThreadLocalthreadLocal.remove(); }, "Thread 1"); // 创建并启动线程2 Thread thread2 = new Thread(() -> { // 设置线程2的ThreadLocalthreadLocal.set("Value for Thread 2"); // 获取并打印线程2的ThreadLocal值 System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get()); // 移除线程2的ThreadLocalthreadLocal.remove(); }, "Thread 2"); // 启动两个线程 thread1.start(); thread2.start(); } } ``` ### 注意事项 - `ThreadLocal`会为每个使用它的线程都创建一个独立的副本,不同线程之间不会相互影响,保证了线程安全,但可能会消耗较多内存。 - 使用完`ThreadLocal`后,要及时调用`remove`方法,防止出现内存泄漏问题,尤其是在使用线程池时,线程会被复用,如果不清理`ThreadLocal`,会导致数据混乱和内存泄漏。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值