原文: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:
<?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的?
publicclass TopicDao {privateConnection conn;①一個非線程安全的變量 publicvoid 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; } }