ThreadLocal的理解与在Spring中的应用
来自:http://neoremind.net/2010/11/threadlocal_learn/
一 引子
首先我们先来看一下Spring框架中是如何使用数据库模板的。
数据库表:
CREATE TABLE users
(
id int AUTO_INCREMENT NOT NULL PRIMARY KEY,
name varchar(32) NOT NULL,
password varchar(32) NOT NULL
);
DAO接口:
public interface UsersDao {
public boolean insert(Users users) throws Exception;
public Users select(int id) throws Exception;
public boolean update(Users users) throws Exception;
public boolean delete(int id) throws Exception;
public List selectAll() throws Exception;
public List selectAllByPage(int curPage, int lineSize) throws Exception;
public int getCount() throws Exception;
}
DAOImpl实现类:
import org.hibernate.Query; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class UsersDaoImpl extends HibernateDaoSupport implements UsersDao { public boolean delete(int id) throws Exception { String hql = "delete from Users where id=:id";// 注意删除的写法! Query q = this.getSession().createQuery(hql); q.setParameter("id", id); if (q.executeUpdate() > 0) { return true; } return false; } public boolean insert(Users users) throws Exception { this.getSession().save(users); return true; } public Users select(int id) throws Exception { String hql = "from Users u where u.id=:id"; Query q = this.getSession().createQuery(hql); q.setParameter("id", id); List l = q.list(); if (l.size() > 0) { return (Users) l.get(0); } return null; } public boolean update(Users users) throws Exception { this.getHibernateTemplate().update(users);//注意更新操作 return true; } public List selectAll() throws Exception { List all = null; String hql = "from Users"; Query q = this.getSession().createQuery(hql); List l = q.list(); if (l.size() > 0) { all = l; } return all; } public List selectAllByPage(int curPage, int lineSize) throws Exception { List all = null; String hql = "from Users"; Query q = this.getSession().createQuery(hql); q.setFirstResult((curPage - 1) * lineSize); q.setMaxResults(lineSize); List l = q.list(); if (l.size() > 0) { all = l; } return all; } public int getCount() throws Exception { String hql = "select count(*) from Users"; Query q = this.getSession().createQuery(hql); if (q.list().size() > 0) { return (Integer) q.list().get(0); } return 0; } }
如何调用:
public class Text {
public static void main(String[] args) throws Exception {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UsersDaoImpl userDaoImpl = (UsersDaoImpl) context.getBean("userDaoImpl");
List l = userDaoImpl.selectAllByPage(5, 10);
if (l != null) {
for (Object u : l) {
System.out.println(((Users) u).getId() + ((Users) u).getName());
}
} else {
System.out.println("没有记录");
}
System.out.println(userDaoImpl.getCount());
}
}
Spring配置文件applicationContext.xml:
Spring配置文件applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName">
<value>com.mysql.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:mysql://127.0.0.1:3306/zzcfront</value>
</property>
<property name="username">
<value>root</value>
</property>
<property name="password">
<value>root</value>
</property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">
org.hibernate.dialect.MySQLDialect
</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>com/ssh/orm/Users.hbm.xml</value>
</list>
</property>
</bean>
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<property name="sessionFactory">
<ref bean="sessionFactory" />
</property>
</bean>
<bean id="userDao" class="com.ssh.dao.UsersDao" abstract="true"></bean>
<bean id="userDaoImpl" class="com.ssh.dao.impl.UsersDaoImpl"
parent="userDao">
<property name="hibernateTemplate">
<ref bean="hibernateTemplate" />
</property>
</bean>
</beans>
二 问题的引出
三 什么是ThreadLocal变量?
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
线程局部变量并不是Java的新发明,很多语言(如IBM IBM XL FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供在语言级支持,而是变相地通过ThreadLocal的类提供支持。
所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。
四 ThreadLocal与同步Synchronized的比较与区别
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。所有这些都是因为多个线程共享了资源造成的。
而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
由于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
五 Spring中数据库模板中是如何使用ThreadLocal的?
public class TopicDao {
private Connection conn;①一个非线程安全的变量
public void addTopic(){
Statement stat = conn.createStatement();②引用非线程安全变量
…
}
}
import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
①使用ThreadLocal保存Connection变量
private static ThreadLocal connThreadLocal = new ThreadLocal();
public static Connection getConnection(){
②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,
并将其保存到线程本地变量中。
if (connThreadLocal.get() == null) {
Connection conn = ConnectionManager.getConnection();
connThreadLocal.set(conn);
return conn;
}else{
return connThreadLocal.get();③直接返回线程本地变量
}
}
public void addTopic() {
④从ThreadLocal中获取线程对应的Connection
Statement stat = getConnection().createStatement();
}
}
六 ThreadLocal深入剖析
public class ThreadLocal
{
private Map values = Collections.synchronizedMap(new HashMap());
public Object get()
{
Thread curThread = Thread.currentThread();
Object o = values.get(curThread);
if (o == null && !values.containsKey(curThread))
{
o = initialValue();
values.put(curThread, o);
}
return o;
}
public void set(Object newValue)
{
values.put(Thread.currentThread(), newValue);
}
public Object initialValue()
{
return null;
}
}