ThreadLocal类

来源:互联网 发布:java接口关键字 编辑:程序博客网 时间:2024/05/16 13:40

本文参考文章:http://www.cnblogs.com/dolphin0520/p/3920407.html  (在其基础上做了总结)

                            http://www.iteye.com/topic/103804

一、ThreadLocal介绍

        ThreadLocal是一个线程级别的局部变量,并非“本地线程”。ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本。

        ThreadLocal类接口很简单,只有4个方法:

        (1) void set(Object value)设置当前线程的线程局部变量的值。

        (2) public Object get()该方法返回当前线程所对应的线程局部变量。

        (3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

        (4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调 用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

        值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应 进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

        概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而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进行了修改的。

        到这里,可能会有朋友想到,既然不需要在线程之间共享这个变量,可以直接这样处理,在每个需要使用数据库连接的方法中具体使用时才创建数据库链接,然后在方法调用完毕再释放这个连接。比如下面这样:

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();    }}

class Dao{    public void insert() {        ConnectionManager connectionManager = new ConnectionManager();        Connection connection = connectionManager.openConnection();                 //使用connection进行操作                 connectionManager.closeConnection();    }}

        这样处理确实也没有任何问题,由于每次都是在方法内部创建的连接,那么线程之间自然不存在线程安全问题。但是这样会有一个致命的影响:导致服务器压 力非常大,并且严重影响程序执行性能。由于在方法中需要频繁地开启和关闭数据库连接,这样不尽严重影响程序执行效率,还可能导致服务器压力巨大。

        那么这种情况下使用ThreadLocal是再适合不过的了,因为ThreadLocal在每个线程中对该变量会创建一个副本,即每个线程内部 都会有一个该变量,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。

        但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

二、java.lang.ThreadLocal<T>的具体实现

        get()方法的实现:

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();}

        第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。

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

        ThreadLocalMap这个类型是ThreadLocal类的一个内部类。

       private T setInitialValue(){T value=initialValue();Thread t=Thread.currentThread();ThreadLocalMap map=getMap(t);if(map!=null){map.set(this,value);}else{createMap(t,value);}return value;}void createMap(Thread t,T firstValue){t.threadLocals=new ThreadLocalMap(this,firstValue);}

        ThreadLocal是如何为每个线程创建变量的副本的:

        首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个 threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

        初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对 Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为 value,存到threadLocals。

        然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

三、ThreadLocal的应用场景

        最常见的ThreadLocal使用场景为用来解决 数据库连接、Session管理等。

        hibernate中典型的ThreadLocal的应用:

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); } }cache(HibernateException ex){ throw new InfrastructureException(ex); } return s;}

        试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao 中,这可够麻烦的。或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是 一般人的想法,但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个 map中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。(避免了对象作为参数传递的麻烦)


0 0
原创粉丝点击