ThreadLocal

匿名 (未验证) 提交于 2019-12-02 23:47:01

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之间数据互不影响
View Code

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); }
View Code

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; }
View Code

     public void remove() {          ThreadLocalMap m = getMap(Thread.currentThread());          if (m != null)              m.remove(this);      }
View Code
  • 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); }
View Code

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操作 }

6、应用

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; }
View Code

总结:

  • 每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。
  • ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
  • 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!