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

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

     threadLocal get获取值时首先获取当前线程,并获取thread的threadLocalMap属性,如果threadLocalmap为空则调用setinitialValue初始化;如果threadLocalMap不为空,则继续获取threadLocalMap中存储的值

4.3remove()

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

 

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

 

 

总结:

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


posted on 2019-07-11 07:52  colorfulworld  阅读(166)  评论(0编辑  收藏  举报