【執行緒】 ThreadLocal
ThreadLocal類提供了執行緒區域性變數。這些變數不同於他們的普通對應物,因為訪問一個變數(通過get或set方法)的每個執行緒都有自己的區域性變數,它獨立於變數的初始化副本。ThreadLocal例項通常是類中的私有靜態欄位,他們希望將狀態與某一個執行緒(例如,使用者ID或事務ID)相關聯。
簡單說,ThreadLocal的作用是提供執行緒內的區域性變數,這種變數線上程的生命週期內起作用,只要在本執行緒內,隨時隨地可以取,於是也就隔離了其他執行緒,主要解決多執行緒中資料資料因併發產生不一致問題,以此保證執行緒安全。
先來看一下
① initialValue()方法:返回該執行緒區域性變數的初始值,是一個延遲呼叫方法,線上程第1次呼叫get()或set(Object)時才執行,並且僅執行1次。
protected T initialValue() {
return null;
}
②get()方法:返回當前執行緒所對應的執行緒區域性變數。
/** * Returns the value in the currentthread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initializedto the value returned * by an invocation of the {@link#initialValue} method. * * @return the current thread's value ofthis 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(); }
③set()方法:設定當前執行緒的執行緒區域性變數的值。
/** * Returns the value in the currentthread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initializedto the value returned * by an invocation of the {@link#initialValue} method. * * @return the current thread's value ofthis 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(); }
④remove()方法:將當前執行緒區域性變數的值刪除,目的是為了減少記憶體的佔用
/**
* Removes the current thread's value forthis thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the currentthread, its value will be
* reinitialized by invoking its {@link#initialValue} method,
* unless its value is {@linkplain #setset} by the current thread
* in the interim. This may result in multiple invocations ofthe
* <tt>initialValue</tt> methodin the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m =getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
【實踐】
ThreadLocal測試:
package com.bjpowernode.drp.util;
/**
* ThreadLocal測試
*
* @author happy
*
*/
public class TestThreadLocal {
// 通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
private static ThreadLocal<Integer> threadNum = new ThreadLocal<Integer>() {
public Integer initialValue() {
return 0;
}
};
// 獲取下一個序列值
public int getNextNum() {
threadNum.set(threadNum.get() + 1);
return threadNum.get();
}
private static class TestClient extends Thread {
private TestThreadLocal testThreadLocal;
public TestClient(TestThreadLocal testThreadLocal) {
this.testThreadLocal = testThreadLocal;
}
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("執行緒[" + Thread.currentThread().getName()
+ "] --> [" + testThreadLocal.getNextNum() + "]");
}
}
public static void main(String[] args) {
TestThreadLocal test = new TestThreadLocal();
// 建立 3個執行緒,共享testThreadLocal,但各自產生序列號
TestClient t1 = new TestClient(test);
TestClient t2 = new TestClient(test);
TestClient t3 = new TestClient(test);
t1.start();
t2.start();
t3.start();
}
}
}
drp中的例項:
不同的執行緒在使用ConnectionManager時,先判斷connectionHolder.get()是否是null,如果是null,則說明當前執行緒還沒有對應的Connection物件,這時建立一個Connection物件並新增到本地執行緒變數中;如果不為null,則說明當前的執行緒已經擁有了Connection物件,直接使用就可以了。這樣,就保證了不同的執行緒使用執行緒相關的Connection,而不會使用其它執行緒的Connection。因此,這個connectionHolder.get()就可以做到在本執行緒內共享了。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* 採用ThreadLocal封裝Connection
*
* @author Administrator
*
*/
public class ConnectionManager {
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
/**
* 得到Connection
*
* @return
*/
public static Connection getConnection() {
// 先從執行緒變數中獲取connection
Connection conn = connectionHolder.get();
// 如果在當前執行緒中沒有繫結相應的Connection
if (conn == null) {
try {
JdbcConfig jdbcConfig = XmlConfigReader.getInstance()
.getJdbcConfig();
Class.forName(jdbcConfig.getDriverName());
conn = DriverManager.getConnection(jdbcConfig.getUrl(),
jdbcConfig.getUserName(), jdbcConfig.getPassword());
// 將Connection設定到ThreadLocal
connectionHolder.set(conn);
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw new ApplicationException("系統錯誤,請聯絡系統管理員");
} catch (SQLException e) {
e.printStackTrace();
throw new ApplicationException("系統錯誤,請聯絡系統管理員");
}
}
return conn;
}
/**
* 關掉connection
*/
public static void closeConnection() {
Connection conn = connectionHolder.get();
if (conn != null) {
try {
conn.close();
// 從ThreadLocal中清除Connection
connectionHolder.remove();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
【比較】
ThreadLocal和Synchonized都用於解決多執行緒併發訪問。synchronized是利用鎖的機制,使變數或程式碼塊在某一時該只能被一個執行緒訪問。而ThreadLocal為每一個執行緒都提供了變數的副本,使得每個執行緒在某一時間訪問到的是各自相應的物件,這樣就隔離了多個執行緒對資料的資料共享。
Synchronized用於執行緒間的資料共享,而ThreadLocal則用於執行緒間的資料隔離。