一、定义
-
ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问, 通常是类中的 private static 字段,是对该字段初始值的一个拷贝,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联
-
Synchronized 用于线程间的数据共享,而 ThreadLocal 则用于线程间的数据隔离。
-
当使用 ThreadLocal 维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。
-
ThreadLocal 是线程 Thread中属性 threadLocals 的管理者。
-
但是要注意,虽然 ThreadLocal 能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用 ThreadLocal 要大。
二、原理和方法
1.ThreadLocal 方法
ThreadLocal 可以存储任何类型的变量对象, get 返回的是一个 Object 对象,但是我们可以通过泛型来制定存储对象的类型。
- public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本
- public void set(T value) { } //set()用来设置当前线程中变量的副本
- public void remove() { } //remove()用来移除当前线程中变量的副本
- protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的,该方法返回该线程局部变量的初始值。
2.使用过程
Thread 在内部是通过 ThreadLocalMap 来维护 ThreadLocal 变量表, 在 Thread 类中有一个 threadLocals 变量,是 ThreadLocalMap 类型的,它就是为每一个线程来存储自身的 ThreadLocal 变量的, ThreadLocalMap 是 ThreadLocal 类的一个内部类,这个 Map 里面的最小的存储单位是一个 Entry, 它使用 ThreadLocal 作为 key, 变量作为 value,这是因为在每一个线程里面,可能存在着多个 ThreadLocal 变量
3.原理
-
ThreadLocal,连接 ThreadLocalMap 和 Thread。来处理 Thread 的 TheadLocalMap 属性,包括 init 初始化属性赋值、get 对应的变量,set 设置变量等。通过当前线程,获取线程上的 ThreadLocalMap 属性,对数据进行 get、set 等操作。
-
ThreadLocalMap,用来存储数据,采用类似 hashmap 机制,存储了以 threadLocal 为 key,需要隔离的数据为 value 的 Entry 键值对数组结构。
-
ThreadLocal,有个 ThreadLocalMap 类型的属性,存储的数据就放在这儿。
4.举例
- 每个人都一张银行卡
- 每个人每张卡都有一定的余额。
- 每个人获取银行卡余额都必须通过该银行的管理系统。
- 每个人都只能获取自己卡持有的余额信息,他人的不可访问。
映射到我们要说的 ThreadLocal
- card 类似于 Thread
- card 余额属性,卡号属性等类似于 Treadlocal 内部属性集合 threadLocals
- cardManager 类似于 ThreadLocal 管理类
三、线程隔离
核心就是ThreadLocalMap类。
threadLocalMap 是 ThreadLocal 类的一个静态内部类,它实现了键值对的设置和获取(对比 Map 对象来理解),每个线程中都有一个独立的 ThreadLocalMap 副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal 类通过操作每一个线程特有的 ThreadLocalMap 副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap 存储的键值对中的键是 this 对象指向的 ThreadLocal 对象,而值就是你所设置的对象了。
四、与同步机制的比较
-
Synchronized 用于线程间的数据共享,而 ThreadLocal 则用于线程间的数据隔离。
-
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
-
ThreadLocal 则从另一个角度来解决多线程的并发访问。ThreadLocal 会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal 提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进 ThreadLocal。
-
概括起来说,对于多线程资源共享的问题,同步机制采用了 “以时间换空间” 的方式,而 ThreadLocal 采用了 “以空间换时间” 的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
五、应用
1.代码示例
package test.threads.threadlocal;
public class ThreadLocalTest {
public static void main(String[] args) throws InterruptedException {
final ThreadLocalTest test = new ThreadLocalTest();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
// 在这里新建了一个线程
Thread thread1 = new Thread() {
public void run() {
/**
* 当这里调用了set方法,进一步调用了ThreadLocal的set方法是,
* 会将ThreadLocal变量存储到该线程的ThreadLocalMap类型的成员变量threadLocals中,
* 注意的是这个threadLocals变量是Thread线程的一个变量,而不是ThreadLocal类的变量。
* */
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
/**
* 这里是获取当前的线程,并把变量存到当前线程的threadlocals中
* */
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
}
运行结果:
1
main
12
Thread-0
1
main
2.应用场景
最常见的 ThreadLocal 使用场景为
用来解决 数据库连接、 Session 管理等