1、定义
threadLocal:更好理解为threadLocalvalue,用于存储本线程中变量,该变量对其他线程而言是不可见的
2、局限
线程之间不能做到数据共享,不管是不是同一个对象的线程还是不同对象的线程,不同线程之间不能做到数据共享,从而无法解决共享对象的更新问题;每个线程往ThreadLocal中读、写数据线程之间都是隔离的,互相之间互不影响
局限相关代码示例:
package com.threadlocal2; public class ThreadLocalT implements Runnable { //ThreadLocal变量初始化 private static ThreadLocal<Integer> ticket=new ThreadLocal<Integer>(){ public Integer initialValue() { System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!"); return 3; } }; public void run(){ while (true){ if(ticket.get()>0){ ticket.set(ticket.get()-1); System.out.println(Thread.currentThread().getName()+"还剩:"+ticket.get()+"张票"); } } } } 运行结: Thread-0还剩:2张票 Thread-1还剩:2张票 Thread-0还剩:1张票 Thread-1还剩:1张票 Thread-0还剩:0张票 Thread-1还剩:0张票 通过结果可见:线程1、2之间数据互不影响
Thread、ThreadLocalMap、ThreadLocal关系
一个Thread有一个ThreadLocalMap,ThreadLocalMap中有许多Entry对象,Entry是一个key:value格式数据结构,其中key是ThreadLocal,value是存储的局部变量值
4、ThreadLocal底层中4个基本方法
4.1 set() :set和createMap以及this
public void set(T value){ Thread t=Thread.currentThread();//获取当前线程 ThreadLocalMap map=getMap(t); //获取当前线程threadLocalMap if(null != map){ map.set(this,value); //如果map不是空,则将ThreadLocal和value放入map中,这里this是ThreadLocal,因为这个代码的类是ThreadLocal.java }else createMap(t,value); //如果map不存在,则新创建一个ThreadLocalMap,并初始化这个map,k是ThreadLocal,v是传过来的value } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocal set赋值时首先获取当前线程,并获取thread的threadLocalMap属性。如果map不空则直接更新value值,如果map空则实例化threadLocalMap并将value值初始化
4.2 get() & setInitialValue() : getEntry
public T get(){ Thread t=Thread.currentThread(); ThreadLocalMap map=getMap(t); //获取当前线程的map if(null != map){ ThreadLocalMap.Entry e = map.getEntry(this); //getEntry方法可以获取map中对应key值的key和value所有内容,相对比与getKey只能获取key值,getValue只能获取value值 if(null != e){ T result=e.value; return result; } }else return setInitialVlaue(); } public T setInitialVlaue(){ T value = initialValue(); Thread t=Thread.currentThread(); ThreadLocalMap map=getMap(t); if(null != map){ map.set(this,value); //初始值为null }else createMap(t,value); } protected T initialValue() { return null; }
threadLocal get获取值时首先获取当前线程,并获取thread的threadLocalMap属性,如果threadLocalmap为空则调用setinitialValue初始化;如果threadLocalMap不为空,则继续获取threadLocalMap中存储的值
4.3remove()
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
- get()方法用于获取当前线程的副本变量值。
- set()方法用于保存当前线程的副本变量值。
- initialValue()为当前线程初始副本变量值。
- remove()方法移除当前前程的副本变量值。
5、ThreadLocalMap底层以及hash冲突处理方式
ThreadLocalMap底层较HashMap更简单,只是数组结构,并没有链表,所以没有next引用,所以遇到hash冲突,并不会想HashMap那样增加链表方式,而是①通过key经过hash计算后确定元素在数组位置,然后判断该位置是否存在值,如果不存在则放入,如果存在,就简单的步长加1或减1,寻找下一个相邻的位置。
*/ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); }
Entry1 |
Entry2 |
Entry3 |
Entry4 |
Entry5 |
Entry6 |
给出良好建议:每个线程只存一个变量,这样不会发生hash冲突,一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能
6、ThreadLocalMap中Entry元素的key是弱引用,value是强引用
static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }
从代码中看到,Entry的key是弱引用,而弱引用在GC时,无论内存是否充足都会被回收;value是强引用,强引用,如果创建ThreadLocal一直运行,那么value一直不会被gc,这样在后续中如果多次执行set()方法后,每个value会一直存在,容易产生内存溢出,所以处理方案是,使用完ThreadLocal之后,记得调用remove方法。
ThreadLocal<Object> threadLocal=new ThreadLocal<Object>(); try{ threadLocal.set(1); //其他业务逻辑代码 }catch(Exception e){ }finally{ threadLocal.remove(); //最后执行remove操作 }
7. ThreadLocal vs Synchronized
Synchronized解决的是线程间数据共享问题;ThreadLocal解决的是线程间数据隔离问题
Synchronized是利用锁机制,使变量或代码块在某一时刻只能被一个线程访问;ThreadLocal为每一个线程体提供了变量副本,使得不同线程在某一时间访问的不是同一个对象,这样隔离了多个线程对数据的数据共享
8、应用场景
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>(); //获取Session public static Session getCurrentSession(){ Session session = threadLocal.get(); //判断Session是否为空,如果为空,将创建一个session,并设置到本地线程变量中 try { if(session ==null&&!session.isOpen()){ if(sessionFactory==null){ rbuildSessionFactory();// 创建Hibernate的SessionFactory }else{ session = sessionFactory.openSession(); } } threadLocal.set(session); } catch (Exception e) { // TODO: handle exception } return session; }
总结:
- 每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。
- ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
- 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案