马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
ThreadLocal
ThreadLocal文档表明:
- This class provides thread-local variables.
- These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own,
- independently initialized copy of the variable.
复制代码 文档大意:这个类提供线程局部变量。这些变量与平常变量的差别之处在于,每个访问它们的线程(通过其get方法或set方法)都有本身的独立初始化的变量副本。
如文档表明所说,ThraedLocal为每个利用该变量的线程提供独立的变量副本,以是每一个线程都可以独立地改变本身的副本,而不会影响别的线程所对应的副本。每个访问ThreadLocal变量的线程都有本身的隔离副本,如许防止了线程之间的干扰,消除了同步的须要。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。说白了ThreadLocal就是存放线程的局部变量的。
对比线程同步
ThreadLocal是修饰变量的,重点是在控制变量的作用域,初志不是为了办理线程并发和线程辩论的,而是为了让变量的种类变的更多更丰富,方便利用。许多开辟语言在语言级别都提供这种作用域的变量范例。
着实要包管线程安全,并不肯定就是要举行同步,两者没有因果关系。同步只是包管共享数据竞争时的本事。如果一个方法原来就不涉及共享数据,那它天然就无需任何同步步调去包管精确性。线程安全,并不肯定就是要举行同步,ThreadLocal目标是线程安全,但不是同步本事。
ThreadLocal和线程同步机制都可以办理多线程中共享变量的访问辩论题目。在同步机制中,通过对象的锁机制包管同一时间只有一个线程访问变量。利用同步机制要求步伐审慎地分析什么时间对变量举行读写,什么时间须要锁定某个对象,什么时间开释对象锁等繁杂的题目,步伐筹划和编写难度相对较大。而ThreadLocal 则从另一个角度来办理多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问辩论。由于每一个线程都拥有本身的变量副本,从而也就没有须要对该变量举行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
固然ThreadLocal可以或许包管多线程访问数据安全,但是由于在每个线程中都创建了副本,以是要思量它对资源的斲丧,比如内存的占用会比倒霉用ThreadLocal要大。对于多线程资源共享的题目,同步机制接纳了“以时间换空间”的方式,而ThreadLocal接纳了“以空间换时间”的方式。前者仅提供一份变量,让差别的线程列队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
利用示例
在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应举行了调解,新版本的API方法分别是void set(T value)、T get()。
ThreadLocal中紧张有三个方法:
- set():设置当火线程的线程局部变量的值。
- get():该方法返回当火线程所对应的线程局部变量。
- remove():删除当火线程的线程局部变量,目标是为了镌汰内存的占用。
- public class ThreadLocalExample {
- // 创建一个 ThreadLocal 变量,用于存储每个线程独立的值
- private static final ThreadLocal<String> threadLocalValue = new ThreadLocal<>();
- public static void main(String[] args) {
- Runnable task1 = () -> {
- // 设置线程局部变量的值
- threadLocalValue.set("Thread-1's Value");
- // 获取并打印线程局部变量的值
- System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
- // 删除线程局部变量的值
- threadLocalValue.remove();
- System.out.println(Thread.currentThread().getName() + " after remove: " + threadLocalValue.get());
- };
- Runnable task2 = () -> {
- // 设置线程局部变量的值
- threadLocalValue.set("Thread-2's Value");
- // 获取并打印线程局部变量的值
- System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
- // 删除线程局部变量的值
- threadLocalValue.remove();
- System.out.println(Thread.currentThread().getName() + " after remove: " + threadLocalValue.get());
- };
- Thread thread1 = new Thread(task1);
- Thread thread2 = new Thread(task2);
- thread1.start();
- thread2.start();
- }
- }
复制代码 除此之外,ThreadLocal提供了一个withInitial()方法同一初始化全部线程的ThreadLocal的值。
- public class ThreadLocalWithInitialExample {
- // 使用 withInitial 方法提供初始值
- private static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
- public static void main(String[] args) {
- Runnable task1 = () -> {
- // 获取并打印线程局部变量的值
- SimpleDateFormat df = dateFormat.get();
- String formattedDate = df.format(new Date());
- System.out.println(Thread.currentThread().getName() + ": " + formattedDate);
- // 删除线程局部变量的值
- dateFormat.remove();
- };
- Runnable task2 = () -> {
- // 获取并打印线程局部变量的值
- SimpleDateFormat df = dateFormat.get();
- String formattedDate = df.format(new Date());
- System.out.println(Thread.currentThread().getName() + ": " + formattedDate);
- // 删除线程局部变量的值
- dateFormat.remove();
- };
- Thread thread1 = new Thread(task1);
- Thread thread2 = new Thread(task2);
- thread1.start();
- thread2.start();
- }
- }
复制代码 ThreadLocal是一种强盛的工具,实用于须要线程隔离的场景,如用户会话、数据库毗连和格式化对象等。利用ThreadLocal可以有用地管理线程本地的数据,克制多线程环境下的竞争和数据划一性题目。但是由于ThreadLocal的生命周期与线程相干,如果在线程池中利用ThreadLocal,须要留意及时调用remove()方法清算线程局部变量,来防止内存走漏。
实现原理
ThreadLocal类本身并不存储线程本地变量的值,而是通过ThreadLocalMap来实现。每个线程内部都有一个ThreadLocalMap实例,ThreadLocal变量作为ThreadLocalMap的键,存储的值是该线程对应的变量值。
set方法起首获取当火线程 Thread 对象,然后获取该线程的 ThreadLocalMap 实例。如果存在,则将值存储在 ThreadLocalMap 中;否则,创建一个新的 ThreadLocalMap。
- public void set(T value) {
- // 获取当前线程
- Thread t = Thread.currentThread();
- // 每个线程 都有一个自己的ThreadLocalMap
- // ThreadLocalMap 里就保存着所有的ThreadLocal变量
- ThreadLocalMap map = getMap(t);
- if (map != null)
- // 向map里添加值
- map.set(this, value);
- else
- // map为null,创建一个 ThreadLocalMap
- createMap(t, value);
- }
- // 全局定义的localMap
- ThreadLocal.ThreadLocalMap threadLocals = null;
- // 获取当前线程所持有的localMap
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
- // 创建,初始化 localMap
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
复制代码 get方法同样先获取当火线程 Thread 对象,然后获取该线程的 ThreadLocalMap 实例。再通过 ThreadLocal 对象作为键从 ThreadLocalMap 中获取值。如果键不存在,则调用 setInitialValue 方法初始化变量。
- public T get() {
- // 获取当前线程
- Thread t = Thread.currentThread();
- // 每个线程 都有一个自己的ThreadLocalMap,
- // ThreadLocalMap里就保存着所有的ThreadLocal变量
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- //ThreadLocalMap的key就是当前ThreadLocal对象实例,
- //多个ThreadLocal变量都是放在这个map中的
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- //从map里取出来的值就是我们需要的这个ThreadLocal变量
- T result = (T)e.value;
- return result;
- }
- }
- // 如果map没有初始化,那么在这里初始化一下
- return setInitialValue();
- }
- // 全局定义的localMap
- ThreadLocal.ThreadLocalMap threadLocals = null;
- // 获取当前线程所持有的localMap
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
复制代码 setInitialValue 方法通过 initialValue 方法获取初始值,并存储在 ThreadLocalMap 中。如果 initialValue 方法未被重写,默认返回 null。
- private T setInitialValue() {
- T value = initialValue();
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- return value;
- }
- protected T initialValue() {
- return null;
- }
复制代码 ThreadLocalMap是一个自界说的哈希表,此中每个元素是一个Entry对象。ThreadLocalMap是一个比力特别的Map,它的每个Entry的key都是一个弱引用。
- static class Entry extends WeakReference<ThreadLocal<?>> {
- /** The value associated with this ThreadLocal. */
- Object value;
- //key就是一个弱引用
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
复制代码 如许筹划的长处是,如果这个变量不再被其他对象利用时,可以主动接纳这个ThreadLocal对象,克制大概的内存走漏。
内存走漏题目
固然ThreadLocalMap中的key是弱引用,当不存在外部强引用的时间,就会主动被接纳。但是Entry中的value依然是强引用,value的引用链条如下:
- Thread --> ThreadLocalMap --> Entry --> value
复制代码 只有当Thread被接纳时,这个value才有被接纳的时机,否则只要线程不退出,value总是会存在一个强引用。但是要求每个Thread都会退出,是一个极其苛刻的要求,对于线程池来说,大部门线程会不绝存在在体系的整个生命周期内,那样的话就会造成value对象出现走漏的大概。
如果get()方法总是访问固定几个不绝存在的ThreadLocal,那么清算动作就不会实验,如果你没有时机调用set()和remove(),那么这个内存走漏依然会发生。以是当你不须要这个ThreadLocal变量时,主动调用remove(),如许是可以或许克制内存走漏的。可以将ThreadLocal的利用和清算放在try-finally块中,确保remove()方法总是会被调用。
- ThreadLocal<MyClass> threadLocal = new ThreadLocal<>();
- try {
- threadLocal.set(new MyClass());
- // 使用线程局部变量
- } finally {
- threadLocal.remove();
- }
复制代码 除此之外,应只管克制将ThreadLocal对象声明为静态变量,特别是在应用服务器或雷同环境中,由于它们的生命周期通常较长,会增长内存走漏的风险。
|