下面咱们看一下ThreadLocal类的官方注释。java
This class provides thread-local variables. These variables differ from数据库
their normal counterparts in that each thread that accesses one (via its安全
{@code get} or {@code set} method) has its own, independently initialized多线程
copy of the variable. {@code ThreadLocal} instances are typically private框架
static fields in classes that wish to associate state with a thread (e.g.,socket
a user ID or Transaction ID).ide
大体的意思是,ThreadLocal提供本地线程变量。这个变量里面的值(经过get方法获取)是和其余线程分割开来的,变量的值只有当前线程能访问到,不像通常的类型好比Person,Student类型的变量,只要访问到声明该变量的对象,便可访问其所有内容,并且各个线程的访问的数据是无差异的。Thread的典型应用是提供一个与程序运行状态相关静态变量,好比一次访问回话的表示符号:USERID,或者一次事务里面的事务id:Transaction ID。学习
线程本地变量是和线程相关的变量,一个线程则一份数据。咱们经过ThreadLocal保存的数据最终是保存在Thread类的ThreadLocalMap threadLocals变量中。ThreadlocalMap是一个Map结构,其中key为咱们声明的ThreadLocal对象,value即为咱们使用ThreadLocal保存的线程本地变量. 测试
当咱们调用ThreadLocal变量set方法时,那么为将TheadLocal做为key,set方法的参数作为value保存在当前线程的threadLocals中.调用get方法时相似,调用get方法时,会去Thread的threadLocals中去寻找key为ThreadLocal 变量的值this
源码以下:
//Thread.threadLocals变量声明 /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; // ThreadLocal set get方法 /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);// getMap方法即去获取当前线程的ThreadLocalMap变量。 if (map != null) map.set(this, value);//以this(ThreadLocal自己)为Key,参数value为值进行保存 else createMap(t, value); } /** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; } /** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ 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(); }
下面是测试代码:
static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>(); @Test public void test01(){ Thread thread1 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; Thread thread2 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; Thread thread3 = new Thread(){ @Override public void run() { stringThreadLocal.set("threadName===>"+Thread.currentThread().getName()); System.out.println(this.getName()+" thread get the value:"+stringThreadLocal.get()); } }; thread1.start(); thread2.start(); thread3.start(); System.out.println("main线程调用set方法以前:"+stringThreadLocal.get()); stringThreadLocal.set("main 线程set的值"); System.out.println("main线程调用set方法以后:"+stringThreadLocal.get()); }
能够看到不一样线程设置的值在该线程是可以正确的取到。因为Thread的threadLocals变量只能在Thread所在的包下才可以访问,所以不能对该变量进行直接访问以验证设置的值在Thread.currentThread对象里面。但若是你调试以上代码,设置值以后访问Thread.currentThread.threadLocals会看到以前设置的值。其中key为声明的ThreadLocal对象。
这算是比较正统的ThreadLocal用法,这可能也是ThreadLocal设计的初衷:用来保存于状态相关的变量,好比访问者的用户信息,事务的事务标识。这里演示一下使用ThreadLocal来传递用户信息,实际上当前流行的大部分权限框架都是使用的ThreadLocal变量来保存用户信息的。
下面是测试代码:
//参数传递测试 @Test public void test02(){ //参数主要利用ThreadLocal是线程的局部变量,只要在同一个线程中,以前设置的值后面就能取到,从而达到参数值传递的效果。 //在前面在线程变量中添加值 stringThreadLocal.set("name"); paramThreadLocal.set(new HashMap(){ { put("id","1"); put("name","xiaoTOT"); put("gender","M"); } }); testParam(); } private void testParam() { //从线程本地变量获取参数 Map map = paramThreadLocal.get(); map.forEach((key,value)->{ System.out.println("key:"+key+" & value="+value); }); }
单例模式的好处无用质疑,能够减小对象的建立。对于那些建立很是费时的对象尤为明显。而且若是可以用单例解决的问题经历使用单例解决,这样能减轻运行时的压力。
对于一个对象假若没有成员变量,单例很是简单,不用去担忧多线程同时对成员变量修改而产生的线程安全问题。
对于一个拥有成员变量的对象使用单例就须要考虑到线程安全问题。多线程访问又能够分为下面两个方面:
a:成员变量须要多线程同步,好比帐户对象(ACCOUNT)中的成员变量余额(amount).amount成员变量须要在多线程的访问下保证各个线程保证绝对的同步,即不管何时线程内的值都是同样。咱们能够经过加同步关键字synchronized,volatile来解决。
b,成员变量不须要线程同步,每一个线程访问本身线程内部的对象。好比一个服务类对数据库的连接成员变量,每一个线程分配一个链接便可。相似这种场景,咱们最简单的方式是使用多例模式来解决。单更好的方式是经过threadLocal来解决。
下面是使用ThreadLocal改造单例模式的示例:
//ThreadLocal在单例模式改造的运用 @Test public void test03(){ //单例模式的好处无用质疑,能够减小对象的建立。对于那些建立很是费时的对象尤为明显。而且若是可以用单例解决的问题经历使用单例解决,这样能减轻运行时的压力。 //1,对于一个对象假若没有成员变量,单例很是简单,不用去担忧多线程同时对成员变量修改而产生的线程安全问题。 //2,对于一个拥有成员变量的对象使用单例就须要考虑到线程安全问题。多线程访问又能够分为下面两个方面: // a:成员变量须要多线程同步,好比帐户对象(ACCOUNT)中的成员变量余额(amount).amount成员变量须要在多线程的访问下保证各个线程保证绝对的同步,即不管何时线程内的值都是同样。 // 咱们能够经过加同步关键字synchronized,volatile来解决。 // b,成员变量不须要线程同步,每一个线程访问本身线程内部的对象。好比一个服务类对数据库的连接成员变量,每一个线程分配一个链接便可。相似这种场景,咱们最简单的方式是使用多例模式来解决。 //单更好的方式是经过threadLocal来解决。 //多例模式 Thread thread = new Thread(){ @Override public void run() { DBConnect dbConnect = new DBConnect(); DemoService demoService = new DemoService(); demoService.setConnect(dbConnect); demoService.doSomeThing(); } }; Thread thread2 = new Thread(){ @Override public void run() { DBConnect dbConnect = new DBConnect(); DemoService demoService = new DemoService(); demoService.setConnect(dbConnect); demoService.doSomeThing(); } }; thread.start(); thread2.start(); // 单例模式改造 // 由DemoService构造器能够看出,构造这个对象是很是耗时的。而且还不能使用单例模式,由于DBConnect是不能多线程访问的。遇到这种状况那就使用ThreadLocal来改造吧。 //若是能修改DemoService源码,修改源码便可。若不能修该源码(好比DemoService是一个三方包)单DemoService不是final的,便可以经过继承修改。 DemoService demoService1 = new ThreadLocalDemoService(); Thread threadA = new Thread(){ @Override public void run() { demoService1.setConnect(new DBConnect()); demoService1.doSomeThing(); } }; Thread threadB = new Thread(){ @Override public void run() { demoService1.setConnect(new DBConnect()); demoService1.doSomeThing(); } }; threadA.start(); threadB.start(); } static class DemoService{ //这个对象不能线程同时访问,应该是一个线程就创建一个链接到数据库。不一样的线程不能使用同一个链接。 DBConnect connect; public DemoService(){ try { Thread.sleep(5l); } catch (InterruptedException e) { e.printStackTrace(); } } public void setConnect(DBConnect connect){ this.connect = connect; } public void doSomeThing(){ connect.updateSomeData(); } } //使用ThreadLocal改形成员变量,使其可使其可使用单例模式 static class ThreadLocalDemoService extends DemoService { ThreadLocal<DBConnect> connectThreadLocal = new ThreadLocal<>(); public ThreadLocalDemoService() { super(); } public void doSomeThing(){ connectThreadLocal.get().updateSomeData(); } public void setConnect(DBConnect dbConnect){ connectThreadLocal.set(dbConnect); } } class DBConnect { private String transactionName = Thread.currentThread().getName()+"的事务"; public void updateSomeData(){ System.out.println(transactionName + " update some data"); } }
其中DemoService中有个成语变量DBConnect connect,因为资源不能同时被连个线程使用,好比socket连接发送数据,或者数据库事务,一个线程不能影响另一个线程的事务。 这个时候咱们没有办法只有对DemoService采用多例模式,单由由于DemoService建立会耗费大量时间。相似的例子不少,好比一个对象中,可能只有少数成员变量不可以多线程访问,大多数是能多线程访问的,这时为了一个成员变量去将单例改为多例也是很是糟糕的。这时若咱们使用ThreadLocal就可以完美解决。
值得注意的是,ThreadLocal随着当前线程的销毁而销毁,若是程序中采用线程池,在上一次任务运行完以后,记得清掉以前ThreadLocal数据。
实际上学习和使用ThreadLocal以前,也百多过不少ThreadLocal相关的文章。最开始是拜读了学习Spring必学的Java基础知识(6)----ThreadLocal@ITEYE这篇文章,才了解到ThreadLocal这个东西。最好为了详细了解,看到了第二篇文章,而且以前看过的关于ThreadLocal的文章与这篇文章内容基本上都同样,都在讲关于Threadloal为解决线程安全问题提供了新思路,当时被看得一头雾水。最好看到第三篇的帖子。而后结合源代码才对ThreadLocal的本质很好的了解。若是你看完本篇文章还不是很是明白,能够详细参阅第三篇引用,这个帖子的讨论仍是很是精彩的,能给你不少启迪做用。须要说明的是第二篇CSDN相关讲解其实是有问题的。你能够看看文章下方的评论。