ThreadLocal类

public class ThreadLocalTest1 implements Runnable{
	private static ThreadLocal<Integer> threadLocalA = new ThreadLocal<Integer>();
	private static ThreadLocal<Integer> threadLocalB = new ThreadLocal<Integer>();
	
	public static void main(String[] args) {
		Thread t1 = new Thread(new ThreadLocalTest1(), "A");
		Thread t2 = new Thread(new ThreadLocalTest1(), "B");
		t1.start();
		t2.start();
	}

	@Override
	public void run() {
		threadLocalA.set((int) (Math.random() * 100D));
		System.out.println("当前线程:" + Thread.currentThread().getName() + ": " + threadLocalA.get());
		
		threadLocalB.set((int) (Math.random() * 100D));
		System.out.println("当前线程:" + Thread.currentThread().getName() + ": " + threadLocalB.get());
	}

}

 输出:java

  当前线程:A: 98
  当前线程:A: 25
  当前线程:B: 28
  当前线程:B: 27mysql

其中: 98, 25 这两个值都会放到本身所属的线程对线A当中web

   28, 27这两个值都会放到本身所属的线程对线A当中sql

                                   

1、对ThreadLocal理解

  ThreadLocal提供一个方便的方式,能够根据不一样的线程存放一些不一样的特征属性,能够方便的在线程中进行存取。  编程

  归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal 采用了“以空间换时间”的方式。前者仅提供一份变量,让不一样的线程排队访问;后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。数组

  ThreadLocal 类主要有以下方法:缓存

    protected T initialValue():设置初始值,默认为null
    public void set(T value):设置一个要保存的值,并会覆盖原来的值
    public T get():得到保存的值,若是没有用过set方法,会获取到初始值
    public void remove():移除保存的值session

ThreadLocal是什么

  首先,它是一个数据结构,有点像HashMap,能够保存"key : value"键值对,可是一个ThreadLocal只能保存一个,而且各个线程的数据互不干扰。数据结构

ThreadLocal<String> localName = new ThreadLocal();
localName.set("占小狼");
String name = localName.get();

  在线程1中初始化了一个ThreadLocal对象localName,并经过set方法,保存了一个值 占小狼,同时在线程1中经过 localName.get()能够拿到以前设置的值,可是若是在线程2中,拿到的将是一个null。多线程

  这是为何,如何实现?不过以前也说了,ThreadLocal保证了各个线程的数据互不干扰。

  看看 set(T value)和 get()方法的源码

public void set(T value) {    
    Thread t = Thread.currentThread();    
    ThreadLocalMap map = getMap(t);    
    if (map != null)        
        map.set(this, value);    
    else        
        createMap(t, value);
}

public T get() {    
    Thread t = Thread.currentThread();    
    ThreadLocalMap map = getMap(t);    
    if (map != null) {        
        ThreadLocalMap.Entry e = map.getEntry(this);        
        if (e != null) {            
            @SuppressWarnings("unchecked")            
            T result = (T)e.value;            
            return result;        
        }    
    }    
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {    
    return t.threadLocals;
}      

  能够发现,每一个线程中都有一个 ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的 threadLocals变量中,当执行set方法中,是从当前线程的 threadLocals变量获取。

  因此在线程1中set的值,对线程2来讲是摸不到的,并且在线程2中从新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰。

  那每一个线程中的 ThreadLoalMap到底是什么?

ThreadLoalMap

本文分析的是1.7的源码。

  从名字上看,能够猜到它也是一个相似HashMap的数据结构,可是在ThreadLocal中,并没实现Map接口。

  在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每个key-value键值对,只不过这里的key永远都是ThreadLocal对象,是否是很神奇,经过ThreadLocal对象的set方法,结果把ThreadLocal对象本身当作key,放进了ThreadLoalMap中。

  这里须要注意的是,ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,因此就不存在链表的状况了。

hash冲突

  没有链表结构,那发生hash冲突了怎么办?

  先看看ThreadLoalMap中插入一个key-value的实现

private void set(ThreadLocal<?> key, Object value) {    
    Entry[] tab = table;    
    int len = tab.length;    
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {        
        ThreadLocal<?> k = e.get();
        if (k == key) {            
            e.value = value;            
            return;        
        }
        if (k == null) {            
            replaceStaleEntry(key, value, i);            
            return;        
        }    
    }
    tab[i] = new Entry(key, value);    
    int sz = ++size;    
    if (!cleanSomeSlots(i, sz) && sz >= threshold)        
        rehash();
}

  每一个ThreadLocal对象都有一个hash值 threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增长一个固定的大小 0x61c88647

  在插入过程当中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程以下:一、若是当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;二、不巧,位置i已经有Entry对象了,若是这个Entry对象的key正好是即将设置的key,那么从新设置Entry中的value;三、很不巧,位置i的Entry对象,和即将设置的key不要紧,那么只能找下一个空位置;

  这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,而后判断该位置Entry对象中的key是否和get的key一致,若是不一致,就判断下一个位置

  能够发现,set和get若是冲突严重的话,效率很低,由于ThreadLoalMap是Thread的一个属性,因此即便在本身的代码中控制了设置的元素个数,但仍是不能控制其它代码的行为。

内存泄露

  ThreadLocal可能致使内存泄漏,为何?先看看Entry的实现:

static class Entry extends WeakReference<ThreadLocal<?>> {    
    /** The value associated with this ThreadLocal. */    
    Object value;
    Entry(ThreadLocal<?> k, Object v) {        
        super(k);        
        value = v;    
    }
}

  经过以前的分析已经知道,当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。

  这就致使了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,若是建立ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

如何避免内存泄露

  既然已经发现有内存泄露的隐患,天然有应对的策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就能够被回收,固然若是调用remove方法,确定会删除对应的Entry对象。

  若是使用ThreadLocal的set方法以后,没有显示的调用remove方法,就有可能发生内存泄露,因此养成良好的编程习惯十分重要,使用完ThreadLocal以后,记得调用remove方法。

ThreadLocal<String> localName = new ThreadLocal();
try {    
    localName.set("占小狼");    
    // 其它业务逻辑
} finally {    
    localName.remove();
}

2、以session为例来理解ThreadLocal  

  在web开发的session中,不一样的线程对应不一样的session,那么如何针对不一样的线程获取对应的session呢?

  咱们能够设想了以下两种方式:

    1.在action中建立session,而后传递给Service,Service再传递给Dao,很明显,这种方式将使代码变得臃肿复杂。

    2.建立一个静态的map,键对应咱们的线程,值对应session,当咱们想获取session时,只须要获取map,而后根据当前的线程就能够获取对应的值。

  咱们看看Hibernate中是如何实现这种状况的:

 

  在Hibernate中是经过使用ThreadLocal来实现的。在getSession方法中,若是ThreadLocal存在session,则返回session,不然建立一个session放入ThreadLocal中。

  总结一下就是在ThreadLocal中存放了一个session。

为何咱们在ThreadLocal存放一个session,这个session就会与一个线程对应呢? 

  实际上ThreadLocal中并无存听任何的对象或引用,在上面的的代码中ThreadLocal的实例threadSession只至关于一个标记的做用。而存放对象的真正位置是正在运行的Thread线程对象,每一个Thread对象中都存放着一个ThreadLocalMap类型threadLocals对象,这是一个映射表map,这个map的键是一个ThreadLocal对象,值就是咱们想存的局部对象。

  咱们以上面的代码为例分析一下:

  当咱们往ThreadLocal中存放变量的时候发生了什么?

  即这行代码时。

  咱们看下ThreadLocal的源码中set()方法的实现。

  若是把这些代码简化的话就一句

    Thread.currentThread().threadLocals.set(this,value);

    Thread.currentThread()获取当前的线程

    threadLocals就是咱们上面说的每一个线程对象中用于存放局部对象的map

  因此set()就是获取到当前线程的map而后把值放进去,咱们发现键是this,也就是当前的ThreadLocal对象,能够发现ThreadLocal对象就是一个标记的做用,咱们根据这个标记找到对应的局部对象。

  若是对比get()方法,能够发现原理都差很少,都是对线程中的threadLocals这个map的操做,我就不解释了。

  ThreadLocal就是一个标记的做用,当咱们在线程中使用ThreadLocal的set()或者get()方法时,实际上是在操做咱们线程自带的threadLocals这个map,多个线程的时候天然就有多个map,这些map互相独立,可是,这些map都是根据一个ThreadLocal对象(由于它是静态的)来做为键存放。

  这样能够在多个线程中,每一个线程存放不同的变量,咱们经过一个ThreadLocal对象,在不一样的线程(经过Thread.currentThread()获取当前线程)中获得不一样的值(不一样线程的threadLocals不同)。

  为何threadLocals要是一个map呢?

  由于咱们可能会在一个类中声明多个ThreadLocal的实例,这样就有多个标记,因此要使用map对应。

原理:

 线程共享变量缓存以下:

  Thread.ThreadLocalMap<ThreadLocalObject>;

    一、Thread: 当前线程,能够经过Thread.currentThread()获取

    二、ThreadLocal:咱们的static ThreadLocal变量。

    三、Object: 当前线程共享变量

 咱们调用ThreadLocal.get方法时,其实是从当前线程中获取ThreadLocalMap<ThreadLocal, Object>,而后根据当前ThreadLocal获取当前线程共享变量Object

 ThreadLocal.set,ThreadLocal.remove其实是一样的道理。

 

 这种存储结构的好处:

  一、线程死去的时候,线程共享变量ThreadLocalMap则销毁。

  二、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,通常来讲ThreadLocal数量不多,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量通常来讲比ThreadLocal数量多),性能提升不少。

 

 关于ThreadLocalMap<ThreadLocalObject>弱引用问题:

  当线程没有结束,可是ThreadLocal已经被回收,则可能致使线程中存在ThreadLocalMap<nullObject>的键值对,形成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。

  虽然ThreadLocal的get,set方法能够清除ThreadLocalMap中key为null的value,可是get,set方法在内存泄露后并不会必然调用,因此为了防止此类状况的出现,咱们有两种手段。

    一、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;

    二、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。

应用场景:

  (1)在线程中存放一些就像session的这种特征变量,会针对不一样的线程,有不一样的值。

  (2)通常 ThreadLocal 做为全局变量使用,示例以下:

public class ConnectionManager {

    // 线程内共享Connection,ThreadLocal一般是全局的,支持泛型
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();

    public static Connection getCurrentConnection() {
        Connection conn = threadLocal.get();
        try {
            if(conn == null || conn.isClosed()) {
                String url = "jdbc:mysql://localhost:3306/test" ;
                conn = DriverManager.getConnection(url , "root" , "root") ;
                threadLocal.set(conn);
            }
        } catch (SQLException e) {
        }
        return conn;
    }

    public static void close() {
        Connection conn = threadLocal.get();
        try {
            if(conn != null && !conn.isClosed()) {
                conn.close();
                threadLocal.remove();
                conn = null;
            }
        } catch (SQLException e) {
        }
    }
}

  (3)ThreadLocal解决共享参数 

  ThreadLocal 也经常使用在多线程环境中,某个方法处理一个业务,须要递归依赖其余方法时,而要在这些方法中共享参数的问题。

  例若有方法 a(),在该方法中调用了方法b(),而在方法 b() 中又调用了方法 c(),即 a–>b—>c。若是 a,b,c 如今都须要使用一个字符串参数 args。

  经常使用的作法是 a(String args)–>b(String args)—c(String args)。可是使用ThreadLocal,能够用另一种方式解决:

    在某个接口中定义一个ThreadLocal 对象
    方法 a()、b()、c() 所在的类实现该接口
    在方法 a()中,使用 threadLocal.set(String args) 把 args 参数放入 ThreadLocal 中
    方法 b()、c() 能够在不用传参数的前提下,在方法体中使用 threadLocal.get() 方法就能够获得 args 参数
示例以下:

interface ThreadArgs {
    ThreadLocal threadLocal = new ThreadLocal();
}

class A implements ThreadArgs {
    public static void a(String args) {
        threadLocal.set(args);
    }
}

class B implements ThreadArgs {
    public static void b() {
        System.out.println(threadLocal.get());
    }
}

class C implements ThreadArgs {
    public static void c() {
        System.out.println(threadLocal.get());
    }
}

public class ThreadLocalDemo {
    public static void main(String[] args) {
        A.a(“hello”);
        B.b();
        C.c();
    }
}

输出结果:

hello
hello

关于 InheritableThreadLocal
  InheritableThreadLocal 类是 ThreadLocal 类的子类。ThreadLocal 中每一个线程拥有它本身的值,与 ThreadLocal 不一样的是,InheritableThreadLocal 容许一个线程以及该线程建立的全部子线程均可以访问它保存的值。

 

跟详细的关于ThreadLocal的分析见这一篇:  ThreadLocal的实现原理 

 

总结:

  ThreadLocal就是用来在类中声明的一个标记,而后经过这个标记就根据不一样Thread对象存取值。

相关文章
相关标签/搜索