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
ThreadLocal提供一个方便的方式,能够根据不一样的线程存放一些不一样的特征属性,能够方便的在线程中进行存取。 编程
归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal 采用了“以空间换时间”的方式。前者仅提供一份变量,让不一样的线程排队访问;后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。数组
ThreadLocal 类主要有以下方法:缓存
protected T initialValue():设置初始值,默认为null
public void set(T value):设置一个要保存的值,并会覆盖原来的值
public T get():得到保存的值,若是没有用过set方法,会获取到初始值
public void remove():移除保存的值session
首先,它是一个数据结构,有点像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
到底是什么?
本文分析的是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冲突了怎么办?
先看看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(); }
在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中并无存听任何的对象或引用,在上面的的代码中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<ThreadLocal, Object>;
一、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<ThreadLocal, Object>弱引用问题:
当线程没有结束,可是ThreadLocal已经被回收,则可能致使线程中存在ThreadLocalMap<null, Object>的键值对,形成内存泄露。(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对象存取值。