ThreadLocal简单介绍

ThreadLocal是什么?

ThreadLocal提供线程本地变量,每个线程拥有本地变量的副本,各个线程之间的变量互不干扰。ThreadLocal实现在多线程环境下去保证变量的安全。以下来源于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.

 

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

看一下简单使用:

public class ThreadLocal2 {

 

    static ThreadLocal<Person>  tl = new ThreadLocal<>();

 

    public static void main(String[] args) {

 

        new Thread(()->{

 

            try {

                TimeUnit.SECONDS.sleep(2);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

 

            System.out.println(tl.get());

 

        }).start();

 

 

        new Thread(()->{

 

            try {

                TimeUnit.SECONDS.sleep(1);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

 

            tl.set(new Person());

 

        }).start();

 

    }

 

    static class Person{

        String name ="zhangsan";

    }

}

 

 

ThreadLocal实现原理

首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。

因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法。例如下面的 set 方法:

 

 public void set(T value) {

        Thread t = Thread.currentThread();

        ThreadLocalMap map = getMap(t);

        if (map != null)

            map.set(this, value);

        else

            createMap(t, value);

    }

get方法

public T get() {   

        Thread t = Thread.currentThread();   

        ThreadLocalMap map = getMap(t);   

        if (map != null)   

            return (T)map.get(this);   

  

        // Maps are constructed lazily.  if the map for this thread   

        // doesn't exist, create it, with this ThreadLocal and its   

        // initial value as its only entry.   

        T value = initialValue();   

        createMap(t, value);   

        return value;   

    }   

createMap方法:

    void createMap(Thread t, T firstValue) {   

        t.threadLocals = new ThreadLocalMap(this, firstValue);   

    }

ThreadLocalMap是个静态的内部类:

    static class ThreadLocalMap {   

    ........   

    }  

最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。

 

使用场景

如上文所述,ThreadLocal 适用于如下两种场景

  • 每个线程需要有自己单独的实例
  • 实例需要在多个方法中共享,但不希望被多线程共享

对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。

对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。

 

1、数据库事务。

事务的实现原理非常简单,只需要在整个请求的处理过程中,用同一个connection开启事务、执行sql、提交事务就可以了。按照这个思路,实现起来也有两种方案,一种就是在第一次执行的时候 ,获取connection,在调用其他函数的时候,显示的传递connection对象。这种方案,只能存在于学习的demo中,无法应用到项目实践。

另一种方案就是通过AOP的方式,对执行数据库事务的函数进行拦截。函数开始前,获取connection开启事务并存储在ThreadLocal中,任何用到connection的地方,从ThreadLocal中获取,函数执行完毕后,提交事务释放connection。

 

2、web项目中的用户登录信息。

web项目中,用户的登录信息通常保存在session中。按照分层的设计理念,往往会被分成controller层、service层、dao层等等,还约定在service层是不能处理request、session等对象的。一种方案是调用service函数的时候,显示的传递用户信息;另一种方案则是用到了ThreadLocal,做一个拦截器,把用户信息放在ThreadLocal中,在任何用到用户信息的时候,只需要从TreadLocal中读取就可以了。

 

ThreadLocal 内存泄漏

1 ThreadLocal很好地解决了线程数据隔离的问题,但是很显然,也引入了另一个空间问题如果线程数量很多,如果ThreadLocal类型的变量很多,将会占用非常大的空间而对于ThreadLocal本身来说,他只是作为key,数据并不会存储在它的内部,所以对于ThreadLocal,ThreadLocalMap内部的这个Entity的key是弱引用

 

如下图所示,实线表示强引用,虚线表示弱引用

对于真实的值是保存在Thread里面的ThreadLocal.ThreadLocalMap threadLocals中的

借助于内部的这个map,通过“壳”ThreadLocal变量的get,可以获取到这个map的真正的值,也就是说,当前线程中持有对真实值value的强引用,而对于ThreadLocal变量本身,如下代码所示,栈中的变量与堆空间中的这个对象,也是强引用的 static ThreadLocal<String> threadLocal = new ThreadLocal<>();不过对于Entity来说,key是弱引用。

 

当一系列的执行结束之后,ThreadLocal的强引用也会消亡,也就是堆与栈之间的从ThreadLocal Ref到ThreadLocal的箭头会断开由于Entity中,对于key是弱引用,所以ThreadLocal变量会被回收(GC时会回收弱引用)而对于线程来说,如果迟迟不结束,那么就会一直存在:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value的强引用,所以value迟迟得不到回收,就会可能导致内存泄漏 ThreadLocalMap的设计中已经考虑到这种情况,所以ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value

 

一旦将value设置为null之后,就斩断了引用于真实内存之间的引用,就能够真正的释放空间,防止内存泄漏

 

但是这只是一种被动的方式,如果这些方法都没有被调用怎么办?

而且现在对于多线程来说,都是使用线程池,那个线程很可能是与应用程序共生死的,怎么办?

那你就每次使用完ThreadLocal变量之后,执行remove方法!!!!

从以上分析也看得出来,由于ThreadLocalMap的生命周期跟Thread一样长,所以很可能导致内存泄漏,弱引用是相当于增加了一种防护手段

通过key的弱引用,以及remove方法等内置逻辑,通过合理的处理,减少了内存泄漏的可能,如果不规范,就仍旧会导致内存泄漏

猜你喜欢

转载自blog.csdn.net/huzhiliayanghao/article/details/106680698
今日推荐