另一篇博文:Hibernet中的ThreadLocal使用 http://www.cnblogs.com/gnivor/p/4440776.html
参考资料:
http://blog.sina.com.cn/s/blog_7ffb8dd5010146i3.html
http://lavasoft.blog.51cto.com/62575/51926/
一、问题的提出
我们知道Session是由SessionFactory负责创建的,而SessionFactory的实现是线程安全的,多个并发的线程可以同时访问一 个SessionFactory并从中获取Session实例,而Session不是线程安全的。Session中包含了数 据库操作相关的状态信息,那么说如果多个线程同时使用一个Session实例进行CRUD,就很有可能导致数据存取的混乱,你能够想像那些你根本不能预测 执行顺序的线程对你的一条记录进行操作的情形吗?
二、 解决方案思路(使用Threadlocal类集合)
早在Java1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新 的选择。ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单, 就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每 一个线程都完全拥有一个该变量。
ThreadLocal这个类本身不是代表线程要访问的变量,这个类的成员变量才是。JDK1.5给ThreadLocal加了泛型功能,即是 ThreadLocal,这个泛型T即是要线程的本地变量。线程通过ThreadLocal的get和set方法去访问这个变量T。
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现(为了简单,没有考虑集合的泛型):
public class ThreadLocal { private Map values = Collections.synchronizedMap(new HashMap()); public Object get() { Thread currentThread = Thread.currentThread(); Object result = values.get(currentThread); if(result == null&&!values.containsKey(currentThread)) { result = initialValue(); values.put(currentThread, result); } return result; } public void set(Object newValue) { values.put(Thread.currentThread(), newValue); } public Object initialValue() { return null; } }
三、解决方案步骤
1、在HibernateUtil类中我们需要定义一个静态的成员变量用于保存当前线程共用的Session
public class HibernateUtil { private static SessionFactory factory; // 使用ThreadLocal集合保存当前业务线程中的SESSION private static ThreadLocal threadlocal= new ThreadLocal(); static { // 第一步:读取HIBERNATE的配置文件,读取hibernate.cfg.xml文件 Configuration con = new Configuration().configure(); // 第二步:创建服务注册构建器对象,通过配置对象中加载所有的配置信息,存放到注册服务中 ServiceRegistryBuilder regBuilder = new ServiceRegistryBuilder() .applySettings(con.getProperties()); // 创建注册服务 ServiceRegistry reg = regBuilder.buildServiceRegistry(); // 第三步:创建会话工厂 factory = con.buildSessionFactory(reg); } public static Session getLocalThreadSession() { Session s = threadlocal.get();// 获取当前线程下的SESSION if (s == null) { s = getFactory().getCurrentSession();// 获取当前线程中的SESSION,需在在Hibernate.cfg.xml文件,具体请看面的说明 threadlocal.set(s);// 将当前SESSION放入到当前线程的容器中保存 } return s; } public static void closeSession() { Session s = threadlocal.get();// 获取当前线程下的SESSION if (s != null) { // s.close();//这里无需将Session关闭,因为该Session是保存在当前线程//中的,线程执行完毕Session自然会销毁 threadlocal.set(null);// 将当前线程中的会话清除 } } }
2、添加OpenSessionInViewFilter过滤器(不要忘了在Web.xml配置该过滤器)
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { Session s = HibernateUtil.getThreadLocalSession(); Transaction t = null; try { // 开始事务 t = s.beginTransaction(); // 进入一系列的过滤链,处理相应的ACTION、业务逻辑及数据层 filterChain.doFilter(request, response); // 提交事务 t.commit(); } catch (Exception e) { if (t != null) t.rollback();//出现异常回滚事务 throw new RuntimeException(e.getMessage(), e); } finally { HibernateUtil.closeSession(); } }
===========================================
说明:关于getCurrentSession()方法:
sessionFactory.getCurrentSession()获取当前线程中的Session, 当调用时,hibernate将session绑定到当前线程,事务结束后,hibernate将session从当前线程中释放,并且关闭 session。当再次调用getCurrentSession()时,将得到一个新的session,并重新开始这一系列工作。这样调用方法如下: Session session = HibernateUtil.getSessionFactory().getCurrentSession();
getCurrentSession和openSession的区别:
1、getCurrentSession创建的session会和绑定到当前线程,而openSession不会。
2、getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而openSession必须手动关闭
3、getCurrentSession需在在Hibernate.cfg.xml文件中添加配置:
<property name="current_session_context_class">thread</property>
四、总结
这种解决方案的优缺点:
1. 优点:使用ThreadLocal除了有避免频繁创建和销毁session的好处外, 还有一个特别大的好处,
就是可以做到多线程的数据隔离, 可以避免多个线程同时操作同一个session
2. 缺点: 如下图
使用拦截器在响应返回时,又重复过滤了一次,延长了响应的时间(改进:我们可以把写在过滤器中的方法写在一个具体的类,用到的时候再调用)
五:注意:
ThreadLocal使用场合主要解决多线程中数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。
1. synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。
2. ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂
本文参考:
http://blog.csdn.net/lufeng20/article/details/24314381
http://www.cnblogs.com/chenying99/articles/3405161.html
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value) | 设置当前线程的线程局部变量的值。 |
public Object get() | 该方法返回当前线程所对应的线程局部变量。 |
public void remove() | 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。 注意,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 |
protected Object initialValue() | 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。 这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。 |
一、知其然
synchronized这类线程同步的机制可以解决多线程并发问题,在这种解决方案下,多个线程访问到的,都是同一份变量的内容。为了防止在多线程访问的过程中,可能会出现的并发错误。不得不对多个线程的访问进行同步,这样也就意味着,多个线程必须先后对变量的值进行访问或者修改,这是一种以延长访问时间来换取线程安全性的策略。
而ThreadLocal类为每一个线程都维护了自己独有的变量拷贝。每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,那就没有任何必要对这些线程进行同步,它们也能最大限度的由CPU调度,并发执行。并且由于每个线程在访问该变量时,读取和修改的,都是自己独有的那一份变量拷贝,变量被彻底封闭在每个访问的线程中,并发错误出现的可能也完全消除了。对比前一种方案,这是一种以空间来换取线程安全性的策略。
来看一个运用ThreadLocal来实现数据库连接Connection对象线程隔离的例子。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class ConnectionManager { private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { @Override protected Connection initialValue() { Connection conn = null; try { conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password"); } catch (SQLException e) { e.printStackTrace(); } return conn; } }; public static Connection getConnection() { return connectionHolder.get(); } public static void setConnection(Connection conn) { connectionHolder.set(conn); } }
通过调用ConnectionManager.getConnection()方法,每个线程获取到的,都是和当前线程绑定的那个Connection对象,第一次获取时,是通过initialValue()方法的返回值来设置值的。通过ConnectionManager.setConnection(Connection conn)方法设置的Connection对象,也只会和当前线程绑定。这样就实现了Connection对象在多个线程中的完全隔离。在Spring容器中管理多线程环境下的Connection对象时,采用的思路和以上代码非常相似。
附:另一个例子
![](https://i-blog.csdnimg.cn/blog_migrate/81178cc93a2a3bb5048d90d76e7ec935.gif)
public class TestNum { private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){ public Integer initialValue() { return 0; } }; public int getNextNum(){ seqNum.set(seqNum.get()+1); return seqNum.get(); } public static void main(String[] args) { TestNum sn = new TestNum(); //三个线程共享SN 产生序列号 ThreadClient t1 = new ThreadClient(sn); ThreadClient t2 = new ThreadClient(sn); ThreadClient t3 = new ThreadClient(sn); t1.start(); t2.start(); t3.start(); } } class ThreadClient extends Thread{ private TestNum sn ; public ThreadClient(TestNum sn){ this.sn = sn; } public void run(){ for(int i = 0 ; i < 3 ; i++){ System.out.println("Thread: "+ Thread.currentThread().getName() + " sn: " + sn.getNextNum()); } } }
二、知其所以然
那么到底ThreadLocal类是如何实现这种“为每个线程提供不同的变量拷贝”的呢?先来看一下ThreadLocal的set()方法的源码是如何实现的:
/** * 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); if (map != null) map.set(this, value); else createMap(t, value); }
线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。
为了加深理解,我们接着看上面代码中出现的getMap和createMap方法的实现:
/** * 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; } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
代码已经说的非常直白,就是获取和设置Thread内的一个叫threadLocals的变量,而这个变量的类型就是ThreadLocalMap,这样进一步验证了上文中的观点:每个线程都有自己独立的ThreadLocalMap对象。打开java.lang.Thread类的源代码,我们能得到更直观的证明:
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
那么接下来再看一下ThreadLocal类中的get()方法,代码是这么说的:
/** * 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) return (T)e.value; } return setInitialValue(); }
再来看setInitialValue()方法:
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ 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; }
这两个方法的代码告诉我们,在获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这当然和前面set()方法的代码是相呼应的。
进一步地,我们可以创建不同的ThreadLocal实例来实现多个变量在不同线程间的访问隔离,为什么可以这么做?因为不同的ThreadLocal对象作为不同键,当然也可以在线程的ThreadLocalMap对象中设置不同的值了。通过ThreadLocal对象,在多线程中共享一个值和多个值的区别,就像你在一个HashMap对象中存储一个键值对和多个键值对一样,仅此而已。
设置到这些线程中的隔离变量,会不会导致内存泄漏呢?ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将作为Thread实例的垃圾被回收掉,所以完全不用担心内存泄漏的问题。在多个线程中隔离的变量,光荣的生,合理的死,真是圆满,不是么?
最后再提一句,ThreadLocal变量的这种隔离策略,也不是任何情况下都能使用的。如果多个线程并发访问的对象实例只允许,也只能创建一个,那就没有别的办法了,老老实实的使用同步机制(synchronized)来访问吧。