threadlocal

来源:互联网 发布:黑暗之魂3男号捏脸数据 编辑:程序博客网 时间:2024/05/18 03:39
如果你定义了一个单实例的java bean,它有若干属性,但是有一个属性不是线程安全的,比如说HashMap。并且碰巧你并不需要在不同的线程中共享这个属性,也就是说这个属性不存在跨线程的意义。那么你不要sychronize这么复杂的东西,ThreadLocal将是你不错的选择。举例来说:复制代码import java.util.HashMap;public class TreadLocalTest { static ThreadLocal map0 = new ThreadLocal(){ @Override protected HashMap initialValue() { System.out.println(Thread.currentThread().getName()+"initialValue"); return new HashMap(); } }; public void run(){ Thread[] runs = new Thread[3]; for(int i=0;i<runs.length;i++){ runs[i]=new Thread(new T1(i)); } for(int i=0;i<runs.length;i++){ runs[i].start(); } } public static class T1 implements Runnable{ int id; public T1(int id0){ id = id0; } public void run() { System.out.println(Thread.currentThread().getName()+":start"); HashMap map = map0.get(); for(int i=0;i<10;i++){ map.put(i, i+id*100); try{ Thread.sleep(100); }catch(Exception ex){ } } System.out.println(Thread.currentThread().getName()+':'+map); } } /** * Main * @param args */ public static void main(String[] args){ TreadLocalTest test = new TreadLocalTest(); test.run(); }}复制代码 输出解释;Thread-1:start Thread-2:start Thread-0:start Thread-2initialValue Thread-1initialValue Thread-0initialValue Thread-1:{0=100, 1=101, 2=102, 3=103, 4=104, 5=105, 6=106, 7=107, 8=108, 9=109} Thread-2:{0=200, 1=201, 2=202, 3=203, 4=204, 5=205, 6=206, 7=207, 8=208, 9=209} Thread-0:{0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=9}可以看到map0 虽然是个静态变量,但是initialValue被调用了三次,通过debug发现,initialValue是从map0.get处发起的。而且每个线程都有自己的map,虽然他们同时执行。进入Theadlocal代码,可以发现如下的片段;复制代码public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }复制代码 这说明ThreadLocal确实只有一个变量,但是它内部包含一个map,针对每个thread保留一个entry,如果对应的thread不存在则会调用initialValue。一.对ThreadLocal的理解  ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。可能很多朋友都知道ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。  这句话从字面上看起来很容易理解,但是真正理解并不是那么容易。  我们还是先来看一个例子:复制代码class ConnectionManager { private static Connection connect = null; public static Connection openConnection() { if(connect == null){ connect = DriverManager.getConnection(); } return connect; } public static void closeConnection() { if(connect!=null) connect.close(); }}复制代码   假设有这样一个数据库链接管理类,这段代码在单线程中使用是没有任何问题的,但是如果在多线程中使用呢?很显然,在多线程中使用会存在线程安全问题:第一,这里面的2个方法都没有进行同步,很可能在openConnection方法中会多次创建connect;第二,由于connect是共享变量,那么必然在调用connect的地方需要使用到同步来保障线程安全,因为很可能一个线程在使用connect进行数据库操作,而另外一个线程调用closeConnection关闭链接。  所以出于线程安全的考虑,必须将这段代码的两个方法进行同步处理,并且在调用connect的地方需要进行同步处理。  这样将会大大影响程序执行效率,因为一个线程在使用connect进行数据库操作的时候,其他线程只有等待。  那么大家来仔细分析一下这个问题,这地方到底需不需要将connect变量进行共享?事实上,是不需要的。假如每个线程中都有一个connect变量,各个线程之间对connect变量的访问实际上是没有依赖关系的,即一个线程不需要关心其他线程是否对这个connect进行了修改的。  到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:   这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。  那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。  但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。二.深入解析ThreadLocal类  在上面谈到了对ThreadLocal的一些理解,那我们下面来看一下具体ThreadLocal是如何实现的。  先了解一下ThreadLocal类提供的几个方法:复制代码class ConnectionManager { private Connection connect = null; public Connection openConnection() { if(connect == null){ connect = DriverManager.getConnection(); } return connect; } public void closeConnection() { if(connect!=null) connect.close(); }} class Dao{ public void insert() { ConnectionManager connectionManager = new ConnectionManager(); Connection connection = connectionManager.openConnection(); //使用connection进行操作 connectionManager.closeConnection(); }}复制代码   get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法,下面会详细说明。  首先我们来看一下ThreadLocal类是如何为每个线程创建一个变量的副本的。  先看下get方法的实现:     第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到键值对,注意这里获取键值对传进去的是 this,而不是当前线程t。  如果获取成功,则返回value值。  如果map为空,则调用setInitialValue方法返回value。  我们上面的每一句来仔细分析:  首先看一下getMap方法中做了什么:    可能大家没有想到的是,在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。  那么我们继续取Thread类中取看一下成员变量threadLocals是什么:    实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:    可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。  然后再继续看setInitialValue方法的具体实现:  很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map,看一下createMap的实现:    至此,可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:  首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。  初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。  然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。  下面通过一个例子来证明通过ThreadLocal能达到在每个线程中创建变量副本的效果:复制代码public class Test { ThreadLocal longLocal = new ThreadLocal(); ThreadLocal stringLocal = new ThreadLocal(); public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final Test test = new Test(); test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); System.out.println(test.getString()); }}复制代码   这段代码的输出结果为:    从这段代码的输出结果可以看出,在main线程中和thread1线程中,longLocal保存的副本值和stringLocal保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和thread1线程中的副本值确实是不同的。  总结一下:  1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;  2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;  3)在进行get之前,必须先set,否则会报空指针异常;   如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。    因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。    看下面这个例子:  复制代码public class Test { ThreadLocal longLocal = new ThreadLocal(); ThreadLocal stringLocal = new ThreadLocal(); public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final Test test = new Test(); System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); System.out.println(test.getString()); }}复制代码   在main线程中,没有先set,直接get的话,运行时会报空指针异常。  但是如果改成下面这段代码,即重写了initialValue方法:复制代码public class Test { ThreadLocal longLocal = new ThreadLocal(){ protected Long initialValue() { return Thread.currentThread().getId(); }; }; ThreadLocal stringLocal = new ThreadLocal(){; protected String initialValue() { return Thread.currentThread().getName(); }; }; public void set() { longLocal.set(Thread.currentThread().getId()); stringLocal.set(Thread.currentThread().getName()); } public long getLong() { return longLocal.get(); } public String getString() { return stringLocal.get(); } public static void main(String[] args) throws InterruptedException { final Test test = new Test(); test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); Thread thread1 = new Thread(){ public void run() { test.set(); System.out.println(test.getLong()); System.out.println(test.getString()); }; }; thread1.start(); thread1.join(); System.out.println(test.getLong()); System.out.println(test.getString()); }}复制代码   就可以直接不用先set而直接调用get了。三.ThreadLocal的应用场景  最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。  如:复制代码private static ThreadLocal connectionHolder= new ThreadLocal() {public Connection initialValue() { return DriverManager.getConnection(DB_URL);}}; public static Connection getConnection() {return connectionHolder.get();}复制代码   下面这段代码摘自:  http://www.iteye.com/topic/103804复制代码private static final ThreadLocal threadSession = new ThreadLocal(); public static Session getSession() throws InfrastructureException { Session s = (Session) threadSession.get(); try { if (s == null) { s = getSessionFactory().openSession(); threadSession.set(s); } } catch (HibernateException ex) { throw new InfrastructureException(ex); } return s;}复制代码
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 私企辞职后档案怎么办 工人档案丢了怎么办 事业单位试用期考核不合格怎么办 工厂不好招人怎么办 办理入职手续怎么办 管理员工不加班怎么办 没有毕业证怎么办入职 不想加班的员工怎么办 邮寄辞职信拒收怎么办 办理退休档案不见了怎么办 办理退休没有档案怎么办 办理退休没有个人档案怎么办 档案被学校丢失怎么办 手动挡汽车脱档怎么办 辞职工作没人交接怎么办 ipad反复重启怎么办 ipad老是重启怎么办 苹果8死机了怎么办 平板突然死机了怎么办 平板卡死机了怎么办 小米ipad死机了怎么办 ipad死机了黑屏怎么办 小米4死机了怎么办 小米平板黑屏是怎么办? ipad死机怎么办不能关机 ipad突然死机了怎么办 京东买ipad坏了怎么办 ipad开机键失灵怎么办 苹果手机按键坏了怎么办 ipad使用中黑屏怎么办 苹果下不可软件怎么办 大学毕业想要当演员怎么办 郑州东站怎么办临时身份证 因招工年龄大造小档桉怎么办 6楼层顶墙边漏水怎么办 信访三级终结后怎么办 领导安排工作不合理怎么办 老板不安排工作怎么办 孩子啃老父母怎么办 孩子变成讨好形怎么办 工厂破产了工资怎么办