ThreadLocal原理和应用

什么是ThreadLocal?

ThreadLocal一般称为线程本地变量,它是一种特殊的线程绑定机制,将变量和线程绑定在一起,为每一个线程维护一个独立的变量副本,通过ThreadLocal可以将对象的可见范围限制在同一个线程内,从而不会与其他线程副本冲突。
说白了就是解决对线程访问共享资源时发生冲突的问题,也算是一种同步的方式,主要是想在多线程环境下去保证成员变量的安全。

ThreadLocal的使用场景

  • 1、比如线程中处理一个非常复杂的业务,可能方法有很多,那么,使用 ThreadLocal 可以代替一些参数的显式传递;

  • 2、比如用来存储用户 Session。Session 的特性很适合 ThreadLocal ,因为 Session 之前当前会话周期内有效,会话结束便销毁。我们先笼统但不正确的分析一次 web 请求的过程:
    用户在浏览器中访问 web 页面; 浏览器向服务器发起请求;服务器上的服务处理程序(例如tomcat)接收请求,并开启一个线程处理请求,期间会使用到 Session;最后服务器将请求结果返回给客户端浏览器。
    从这个简单的访问过程我们看到正好这个 Session是在处理一个用户会话过程中产生并使用的,如果单纯的理解一个用户的一次会话对应服务端一个独立的处理线程,那用 ThreadLocal 在存储 Session ,简直是再合适不过了。
    但是例如 tomcat 这类的服务器软件都是采用了线程池技术的,并不是严格意义上的一个会话对应一个线程。并不是说这种情况就不适合 ThreadLocal 了,而是要在每次请求进来时先清理掉之前的 Session ,一般可以用拦截器、过滤器来实现。

  • 3、在一些多线程的情况下,如果用线程同步的方式,当并发比较高的时候会影响性能,可以改为 ThreadLocal的方式,例如高性能序列化框架 Kyro 就要用 ThreadLocal 来保证高性能和线程安全;

  • 4、还有像线程内上线文管理器、数据库连接等可以用到 ThreadLocal;

ThreadLocal的使用方式

ThreadLocal 的使用非常简单,最核心的操作就是四个:创建、创建并赋初始值、赋值、取值。

1、创建

ThreadLocal<String> mLocal = new ThreadLocal<>();

2、创建并赋初值。下面代码表示创建了一个 String 类型的 ThreadLocal 并且重写了 initialValue 方法,并返回初始字符串,之后调用 get() 方法获取的值便是initialValue 方法返回的值。

ThreadLocal<String> mLocal = new ThreadLocal<String>(){
            @Override
            protected String initialValue(){
                return "init value";
            }
        };
System.out.println(mLocal.get());

3、设置值

 mLocal.set("hello");

4、取值

mLocal.get()

ThreadLocal的实现原理

public class ThreadLocal  
{  
  private Map values = Collections.synchronizedMap(new HashMap());  
  public Object get()  
  {  
      Thread curThread = Thread.currentThread();  
      Object o = values.get(curThread);  
      if (o == null && !values.containsKey(curThread))  
      {  
          o = initialValue();  
          values.put(curThread, o);  
      }  
      return o;  
  }  
  public void set(Object newValue)  
  {  
      values.put(Thread.currentThread(), newValue);  
    }  
    public Object initialValue()  
    {  
      return null;  
  }  
}

底层维护了一个线程安全的Map,Map就以键值对的形式存储线程对象和线程对象的副本值

ThreadLocal的内存泄漏问题

实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。

所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap 中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。

ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。

ThreadLocal与其他同步机制的区别

ThreadLocal与普通的同步机制都是为了解决多线程访问共享资源时会产生冲突的问题,普通的同步机制是控制了线程对共享资源的访问时间而避免冲突的,他是多个线程进行通信的有效方式,而ThreadLocal则是在空间上对共享数据进行了隔离,从根本上来说,数据已经不在共享了以此避免冲突。因此两种方式是在不同的角度所实现的线程安全。当我们需要多线程之间进行通信就使用同步机制,需要隔离多个线程之间的共相冲突,就是用ThreadLocal。

猜你喜欢

转载自blog.csdn.net/SfandZero/article/details/84892780