ThreadLocal详解
一、介绍:
ThreadLocal是Thread的局部变量,用于编多线程程序,对解决多线程程序的并发问题有一定的启示作用。ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

二、ThreadLocal类接口很简单,只有4个方法:
1、void get()
2、void set(Object value)
1 public void set(T value) { 2 //1、获取当前线程 3 Thread t = Thread.currentThread(); 4 //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空, 5 //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值 6 ThreadLocalMap map = getMap(t); 7 if (map != null) 8 map.set(this, value); 9 else 10 // 初始化thradLocalMap 并赋值 11 createMap(t, value); 12 }
3、public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 1.5 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。一般线程池中对线程的管理都会采用线程复用的方式,复用的这些线程可能永远都不会结束,也不会被垃圾回收,因此需要我们手动remove()当前线程局部变量的值。
4、protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
三、场景:
1、说明:
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。
2、应用:
场景一:存储用户Session
1 private static final ThreadLocal threadSession = new ThreadLocal(); 2 3 public static Session getSession() throws InfrastructureException { 4 Session s = (Session) threadSession.get(); 5 try { 6 if (s == null) { 7 s = getSessionFactory().openSession(); 8 threadSession.set(s); 9 } 10 } catch (HibernateException ex) { 11 throw new InfrastructureException(ex); 12 } 13 return s; 14 }
场景二:数据库连接,处理数据库事务
场景三、数据跨层传递(controller,service, dao)
每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。
例如,用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。
在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦。
比如说我们是一个用户系统,那么当一个请求进来的时候,一个线程会负责执行这个请求,然后这个请求就会依次调用service-1()、service-2()、service-3()、service-4(),这4个方法可能是分布在不同的类中的。这个例子和存储session有些像。
1 package com.kong.threadlocal; 2 3 4 public class ThreadLocalDemo05 { 5 public static void main(String[] args) { 6 User user = new User("jack"); 7 new Service1().service1(user); 8 } 9 10 } 11 12 class Service1 { 13 public void service1(User user){ 14 //给ThreadLocal赋值,后续的服务直接通过ThreadLocal获取就行了。 15 UserContextHolder.holder.set(user); 16 new Service2().service2(); 17 } 18 } 19 20 class Service2 { 21 public void service2(){ 22 User user = UserContextHolder.holder.get(); 23 System.out.println("service2拿到的用户:"+user.name); 24 new Service3().service3(); 25 } 26 } 27 28 class Service3 { 29 public void service3(){ 30 User user = UserContextHolder.holder.get(); 31 System.out.println("service3拿到的用户:"+user.name); 32 //在整个流程执行完毕后,一定要执行remove 33 UserContextHolder.holder.remove(); 34 } 35 } 36 37 class UserContextHolder { 38 //创建ThreadLocal保存User对象 39 public static ThreadLocal<User> holder = new ThreadLocal<>(); 40 } 41 42 class User { 43 String name; 44 public User(String name){ 45 this.name = name; 46 } 47 } 48 49 执行的结果: 50 51 service2拿到的用户:jack 52 service3拿到的用户:jack
场景四、Spring使用ThreadLocal解决线程安全问题
TopicDao:非线程安全
1 2 public class TopicDao { 3 //①一个非线程安全的变量 4 private Connection conn; 5 public void addTopic(){ 6 //②引用非线程安全变量 7 Statement stat = conn.createStatement(); 8 … 9 }
TopicDao:线程安全
1 2 import java.sql.Connection; 3 import java.sql.Statement; 4 public class TopicDao { 5 6 //①使用ThreadLocal保存Connection变量 7 private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>(); 8 public static Connection getConnection(){ 9 10 //②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection, 11 //并将其保存到线程本地变量中。 12 if (connThreadLocal.get() == null) { 13 Connection conn = ConnectionManager.getConnection(); 14 connThreadLocal.set(conn); 15 return conn; 16 }else{ 17 //③直接返回线程本地变量 18 return connThreadLocal.get(); 19 } 20 } 21 public void addTopic() { 22 23 //④从ThreadLocal中获取线程对应的 24 Statement stat = getConnection().createStatement(); 25 }
四、ThreadLocal与Synchronized的区别:
ThreadLocal和Synchonized都用于解决对于多线程资源共享的问题
但是ThreadLocal与synchronized有本质的区别:
1、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
2、Synchronized是利用同步锁机制,使变量或代码块在某一时刻只能被一个线程访问,采用了“以时间换空间”的方式。而ThreadLocal为每一个线程都提供了一份独立变量副本,因此可以同时访问而互不影响,采用了“以空间换时间”的方式。
五、ThreadLocal与Thread,ThreadLocalMap之间的关系 :

1、每个Thread线程内部都有一个Map(ThreadLocalMap)。
2、Map里面存储ThreadLocal对象(key)和线程的变量副本(value)。
3、Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
六、ThreadLocal代码案例:
从这个示例中我们可以看到,两个线程分表获取了自己线程存放的变量,他们之间变量的获取并不会错乱。
1 public class ThreadLocaDemo { 2 3 private static ThreadLocal<String> localVar = new ThreadLocal<String>(); 4 5 static void print(String str) { 6 //打印当前线程中本地内存中本地变量的值 7 System.out.println(str + " :" + localVar.get()); 8 //清除本地内存中的本地变量 9 localVar.remove(); 10 } 11 public static void main(String[] args) throws InterruptedException { 12 13 new Thread(new Runnable() { 14 public void run() { 15 ThreadLocaDemo.localVar.set("local_A"); 16 print("A"); 17 //打印本地变量 18 System.out.println("after remove : " + localVar.get()); 19 20 } 21 },"A").start(); 22 23 Thread.sleep(1000); 24 25 new Thread(new Runnable() { 26 public void run() { 27 ThreadLocaDemo.localVar.set("local_B"); 28 print("B"); 29 System.out.println("after remove : " + localVar.get()); 30 31 } 32 },"B").start(); 33 } 34 } 35 36 A :local_A 37 after remove : null 38 B :local_B 39 after remove : null 40
七、问题解析:
1、value值为什么不存在并发问题?
因为只有一个线程能访问,每个线程都有自己的threadlocal 变量,不同的threadlocal对应于不同的value值,他们之间互不影响。ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
2、ThreadLocal 内存泄露的原因
(1)没有手动删除这个 Entry
(2)CurrentThread 当前线程依然运行
由于ThreadLocalMap 的生命周期跟 Thread 一样长,对于重复利用的线程来说,如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏。
3、为什么 key 要用弱引用?
事实上,在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的.这就意味着使用threadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaI 调用 get()/set()/remove() 中的任一方法的时候会被清除,从而避免内存泄漏。
4、如何正确的使用ThreadLocal?
将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
浙公网安备 33010602011771号