一、概述 //創(chuàng)建線程局部變量session,用來保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal(); /** * 獲取當(dāng)前線程中的Session * @return Session * @throws HibernateException */ public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // 如果Session還沒有打開,則新開一個(gè)Session if (s == null) { s = sessionFactory.openSession(); session.set(s); //將新開的Session保存到線程局部變量中 } return s; } public static void closeSession() throws HibernateException { //獲取線程局部變量,并強(qiáng)制轉(zhuǎn)換為Session類型 Session s = (Session) session.get(); session.set(null); if (s != null) s.close(); } } 在這個(gè)類中,由于沒有重寫ThreadLocal的initialValue()方法,則首次創(chuàng)建線程局部變量session其初始值為null,第一次調(diào)用currentSession()的時(shí)候,線程局部變量的get()方法也為null。因此,對(duì)session做了判斷,如果為null,則新開一個(gè)Session,并保存到線程局部變量session中,這一步非常的關(guān)鍵,這也是“public static final ThreadLocal session = new ThreadLocal()”所創(chuàng)建對(duì)象session能強(qiáng)制轉(zhuǎn)換為Hibernate Session對(duì)象的原因。 2、另外一個(gè)實(shí)例 創(chuàng)建一個(gè)Bean,通過不同的線程對(duì)象設(shè)置Bean屬性,保證各個(gè)線程Bean對(duì)象的獨(dú)立性。 /** * Created by IntelliJ IDEA. * User: leizhimin * Date: 2007-11-23 * Time: 10:45:02 * 學(xué)生 */ public class Student { private int age = 0; //年齡 public int getAge() { return this.age; } public void setAge(int age) { this.age = age; } } /** * Created by IntelliJ IDEA. * User: leizhimin * Date: 2007-11-23 * Time: 10:53:33 * 多線程下測(cè)試程序 */ public class ThreadLocalDemo implements Runnable { //創(chuàng)建線程局部變量studentLocal,在后面你會(huì)發(fā)現(xiàn)用來保存Student對(duì)象 private final static ThreadLocal studentLocal = new ThreadLocal(); public static void main(String[] agrs) { ThreadLocalDemo td = new ThreadLocalDemo(); Thread t1 = new Thread(td, "a"); Thread t2 = new Thread(td, "b"); t1.start(); t2.start(); } public void run() { accessStudent(); } /** * 示例業(yè)務(wù)方法,用來測(cè)試 */ public void accessStudent() { //獲取當(dāng)前線程的名字 String currentThreadName = Thread.currentThread().getName(); System.out.println(currentThreadName + " is running!"); //產(chǎn)生一個(gè)隨機(jī)數(shù)并打印 Random random = new Random(); int age = random.nextInt(100); System.out.println("thread " + currentThreadName + " set age to:" + age); //獲取一個(gè)Student對(duì)象,并將隨機(jī)數(shù)年齡插入到對(duì)象屬性中 Student student = getStudent(); student.setAge(age); System.out.println("thread " + currentThreadName + " first read age is:" + student.getAge()); try { Thread.sleep(500); } catch (InterruptedException ex) { ex.printStackTrace(); } System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge()); } protected Student getStudent() { //獲取本地線程變量并強(qiáng)制轉(zhuǎn)換為Student類型 Student student = (Student) studentLocal.get(); //線程首次執(zhí)行此方法的時(shí)候,studentLocal.get()肯定為null if (student == null) { //創(chuàng)建一個(gè)Student對(duì)象,并保存到本地線程變量studentLocal中 student = new Student(); studentLocal.set(student); } return student; } } 運(yùn)行結(jié)果: a is running! thread a set age to:76 b is running! thread b set age to:27 thread a first read age is:76 thread b first read age is:27 thread a second read age is:76 thread b second read age is:27 可以看到a、b兩個(gè)線程age在不同時(shí)刻打印的值是完全相同的。這個(gè)程序通過妙用ThreadLocal,既實(shí)現(xiàn)多線程并發(fā),游兼顧數(shù)據(jù)的安全性。 四、總結(jié) ThreadLocal使用場(chǎng)合主要解決多線程中數(shù)據(jù)數(shù)據(jù)因并發(fā)產(chǎn)生不一致問題。ThreadLocal為每個(gè)線程的中并發(fā)訪問的數(shù)據(jù)提供一個(gè)副本,通過訪問副本來運(yùn)行業(yè)務(wù),這樣的結(jié)果是耗費(fèi)了內(nèi)存,單大大減少了線程同步所帶來性能消耗,也減少了線程并發(fā)控制的復(fù)雜度。 ThreadLocal不能使用原子類型,只能使用Object類型。ThreadLocal的使用比synchronized要簡(jiǎn)單得多。 ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別。synchronized是利用鎖的機(jī)制,使變量或代碼塊在某一時(shí)該只能被一個(gè)線程訪問。而ThreadLocal為每一個(gè)線程都提供了變量的副本,使得每個(gè)線程在某一時(shí)間訪問到的并不是同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。而Synchronized卻正好相反,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享。 Synchronized用于線程間的數(shù)據(jù)共享,而ThreadLocal則用于線程間的數(shù)據(jù)隔離。 當(dāng)然ThreadLocal并不能替代synchronized,它們處理不同的問題域。Synchronized用于實(shí)現(xiàn)同步機(jī)制,比ThreadLocal更加復(fù)雜。 五、ThreadLocal使用的一般步驟 1、在多線程的類(如ThreadDemo類)中,創(chuàng)建一個(gè)ThreadLocal對(duì)象threadXxx,用來保存線程間需要隔離處理的對(duì)象xxx。 2、在ThreadDemo類中,創(chuàng)建一個(gè)獲取要隔離訪問的數(shù)據(jù)的方法getXxx(),在方法中判斷,若ThreadLocal對(duì)象為null時(shí)候,應(yīng)該new()一個(gè)隔離訪問類型的對(duì)象,并強(qiáng)制轉(zhuǎn)換為要應(yīng)用的類型。 3、在ThreadDemo類的run()方法中,通過getXxx()方法獲取要操作的數(shù)據(jù),這樣可以保證每個(gè)線程對(duì)應(yīng)一個(gè)數(shù)據(jù)對(duì)象,在任何時(shí)刻都操作的是這個(gè)對(duì)象。 通常在多線程中,當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,所以每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本 實(shí)現(xiàn)線程本地類其實(shí)不難:以當(dāng)前線程為key,要保存的對(duì)象為value public class ThreadLocalSample { private Map map = Collections.synchronizedMap(new HashMap());
public void set(Object value) { map.put(Thread.currentThread(), value); } public Object get() { Object value = map.get(Thread.currentThread()); return value; } } ThreadLocal可用在Servlet的過濾器中,比如:每一個(gè)請(qǐng)求都由一個(gè)Connection來操作數(shù)據(jù)庫,由于Servlet只有一個(gè)實(shí)例,所以應(yīng)該是每個(gè)線程用一個(gè)連接,而不是所有線程用一個(gè)Connection,因此需要用到ThreadLocal類;由于Servlet是在Filter鏈的調(diào)用中點(diǎn)執(zhí)行,即Filter1->Filter2->...->Servlet->Filter2->Filter1,因此:可以用一個(gè)Filter,在這個(gè)Filter調(diào)用下一個(gè)Filter前初始化連接,并將其放入ThreadLocal中,在調(diào)用又回到這個(gè)Filter時(shí),得到連接并關(guān)閉。 代碼如下: public class TransactionManageFilter implements Filter { private FilterConfig config; public void init(FilterConfig config) throws ServletException { } public void destroy() { Connection conn = ConnectionManager.currentConnection(); try { conn.setAutoCommit(false); chain.doFilter(request, response, chain); conn.commit(); } catch (Exception e) { conn.rollback(); } finally { try { conn.setAutoCommit(true); conn.close(); ConnectionManager.removeConnection(); } catch (Exception e) {} } } } public class ConnectionManager { private static ThreadLocal currConn = new ThreadLocal();
public static Connection currentConnection() { Object obj = currConn.get(); if (obj != null) { return (Connection)obj; } else { Connection conn = ConnectionFactory.getConnection(); currConn.set(conn); return conn; } } public static void removeConnection() { Object obj = currConn.get(); if (obj != null) { currConn.set(null); } } } |
|