谈谈Java中的ThreadLocal
看看源码web
线程独享变量?算法
ThreadLocal介绍&跳出误区
ThreadLocal通常称为线程本地变量,它是一种特殊的线程绑定机制,将变量与线程绑定在一块儿,为每个线程维护一个独立的变量副本。经过ThreadLocal能够将对象的可见范围限制在同一个线程内。spring
跳出误区编程
须要重点强调的的是,不要拿ThreadLocal和synchronized作类比,由于这种比较压根就是无心义的!sysnchronized是一种互斥同步机制,是为了保证在多线程环境下对于共享资源的正确访问。而ThreadLocal从本质上讲,无非是提供了一个“线程级”的变量做用域,它是一种线程封闭(每一个线程独享变量)技术,更直白点讲,ThreadLocal能够理解为将对象的做用范围限制在一个线程上下文中,使得变量的做用域为“线程级”。tomcat
没有ThreadLocal的时候,一个线程在其声明周期内,可能穿过多个层级,多个方法,若是有个对象须要在此线程周期内屡次调用,且是跨层级的(线程内共享),一般的作法是经过参数进行传递;而ThreadLocal将变量绑定在线程上,在一个线程周期内,不管“你身处何地”,只需经过其提供的get方法就可轻松获取到对象。极大地提升了对于“线程级变量”的访问便利性。安全
来看个简单的例子服务器
假设咱们要为每一个线程关联一个惟一的序号,在每一个线程周期内,咱们须要屡次访问这个序号,这时咱们就可使用ThreadLocal了.(固然下面这个例子没有彻底体现出跨层级跨方法的调用,理解就能够了)
package concurrent; import java.util.concurrent.atomic.AtomicInteger; /** * Created by chengxiao on 2016/12/12. */
public class ThreadLocalDemo { public static void main(String []args){ for(int i=0;i<5;i++){ final Thread t = new Thread(){ @Override public void run(){ System.out.println("当前线程:"+Thread.currentThread().getName()+",已分配ID:"+ThreadId.get()); } }; t.start(); } } static class ThreadId{ //一个递增的序列,使用AtomicInger原子变量保证线程安全
private static final AtomicInteger nextId = new AtomicInteger(0); //线程本地变量,为每一个线程关联一个惟一的序号
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return nextId.getAndIncrement();//至关于nextId++,因为nextId++这种操做是个复合操做而非原子操做,会有线程安全问题(可能在初始化时就获取到相同的ID,因此使用原子变量
} }; //返回当前线程的惟一的序列,若是第一次get,会先调用initialValue,后面看源码就了解了
public static int get() { return threadId.get(); } } }
执行结果,能够看到每一个线程都分配到了一个惟一的ID,同时在此线程范围内的"任何地点",咱们均可以经过ThreadId.get()这种方式直接获取。
当前线程:Thread-4,已分配ID:1 当前线程:Thread-0,已分配ID:0 当前线程:Thread-2,已分配ID:3 当前线程:Thread-1,已分配ID:4 当前线程:Thread-3,已分配ID:2
看看源码
set操做,为线程绑定变量
public void set(T value) { Thread t = Thread.currentThread();//1.首先获取当前线程对象
ThreadLocalMap map = getMap(t);//2.获取该线程对象的ThreadLocalMap
if (map != null) map.set(this, value);//若是map不为空,执行set操做,以当前threadLocal对象为key,实际存储对象为value进行set操做
else createMap(t, value);//若是map为空,则为该线程建立ThreadLocalMap
}
能够看到,ThreadLocal不过是个入口,真正的变量是绑定在线程上的。
ThreadLocalMap getMap(Thread t) { return t.threadLocals;//线程对象持有ThreadLocalMap的引用 }
下面给是Thread类中的定义,每一个线程对象都拥有一个ThreadLocalMap对象
ThreadLocal.ThreadLocalMap threadLocals = null;
如今,咱们能看出ThreadLocal的设计思想了:
1.ThreadLocal仅仅是个变量访问的入口;
2.每个Thread对象都有一个ThreadLocalMap对象,这个ThreadLocalMap持有对象的引用;
3.ThreadLocalMap以当前的threadlocal对象为key,以真正的存储对象为value。get时经过threadlocal实例就能够找到绑定在当前线程上的对象。
乍看上去,这种设计确实有些绕。咱们彻底能够在设计成Map<Thread,T>这种形式,一个线程对应一个存储对象。ThreadLocal这样设计的目的主要有两个:
一是能够保证当前线程结束时相关对象能尽快被回收;二是ThreadLocalMap中的元素会大大减小,咱们都知道map过大更容易形成哈希冲突而致使性能变差。
咱们再来看看get方法
public T get() { Thread t = Thread.currentThread();//1.首先获取当前线程
ThreadLocalMap map = getMap(t);//2.获取线程的map对象
if (map != null) {//3.若是map不为空,以threadlocal实例为key获取到对应Entry,而后从Entry中取出对象便可。
ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue();//若是map为空,也就是第一次没有调用set直接get(或者调用过set,又调用了remove)时,为其设定初始值
}
setInitialValue
1 private T setInitialValue() { 2 T value = initialValue();//获取初始值 3 Thread t = Thread.currentThread(); 4 ThreadLocalMap map = getMap(t); 5 if (map != null) 6 map.set(this, value); 7 else 8 createMap(t, value); 9 return value; 10 }
initialValue方法,默认是null,访问权限是protected,即容许重写。
1 protected T initialValue() { 2 return null; 3 }
谈到这儿,咱们应该已经对ThreadLocal的设计目的及设计思想有必定的了解了。
线程独享变量?
还有一个会引发疑惑的问题,咱们说ThreadLocal为每个线程维护一个独立的变量副本,那么是否是说各个线程之间真正的作到对于对象的“彻底自治”而不对其余线程的对象产生影响呢?其实这已经不属于对于ThreadLocal的讨论,而是你出于何种目的去使用ThreadLocal。若是咱们为一个线程关联的对象是“彻底独享”的,也就是每一个线程拥有一整套的新的 栈中的对象引用+堆中的对象,那么这种状况下是真正的完全的“线程独享变量”,至关于一种深度拷贝,每一个线程本身玩本身的,对该对象作任何的操做也不会对别的线程有任何影响。
另外一种更广泛的状况,所谓的独享变量副本,其实也就是每一个线程都拥有一个独立的对象引用,而堆中的对象仍是线程间共享的,这种状况下,天然仍是会涉及到对共享资源的访问操做,依然会有线程不安全的风险。因此说,ThreadLocal没法解决线程安全问题。
因此,需不须要彻底独享变量,进行彻底隔离,就取决于你的应用场景了。能够想象,对象过大的时候,若是每一个线程都有这么一份“深拷贝”,并发又比较大,对于服务器的压力天然是很大的。像web开发中的servlet,servlet是线程不安全的,一请求一线程,多个线程共享一个servlet对象;而早期的CGI设计中,N个请求就对应N个对象,并发量大了以后性能天然就不好。
ThreadLocal在spring的事务管理,包括Hibernate的session管理等都有出现,在web开发中,有时会用来管理用户会话 HttpSession,web交互中这种典型的一请求一线程的场景彷佛比较适合使用ThreadLocal,可是须要特别注意的是,因为此时session与线程关联,而tomcat这些web服务器多会采用线程池机制,也就是说线程是可复用的,因此在每一次进入的时候都须要从新进行set,或者在结束时及时remove。
做者: dreamcatcher-cx
出处: <http://www.cnblogs.com/chengxiao/>
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在页面明显位置给出原文连接。