ThreadLocal是什么?
早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路。使用這個工具類可以很簡潔地編寫出優(yōu)美的多線程程序。
java.lang.ThreadLocal提供了線程局部 (thread-local) 變量。這些變量不同于它們的普通對應(yīng)物,因為訪問某個變量(通過其 get 或 set 方法)的每個線程都有自己的局部變量,它獨立于變量的初始化副本。ThreadLocal 實例通常是類中的 private static 字段,它們希望將狀態(tài)與某一個線程(例如,用戶 ID 或事務(wù) ID)相關(guān)聯(lián)。
每個線程都保持對其線程局部變量副本的隱式引用,只要線程是活動的并且 ThreadLocal 實例是可訪問的;在線程消失之后,其線程局部實例的所有副本都會被GC回收(除非存在對這些副本的其他引用)。
ThreadLocal接口方法
- ThreadLocal對外提供了四個方法:
- void set(T value) 設(shè)置當(dāng)前局部變量的值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
- product T initialValue() 返回該線程局部變量的初始值,該方法是一個protected的方法,方法內(nèi)部并沒有具體實現(xiàn)代碼,顯然是為了讓子類覆蓋而設(shè)計的。這個方法是一個延遲調(diào)用方法,在線程第1次調(diào)用get()或set(Object)時才執(zhí)行,并且僅執(zhí)行1次。但是如果在調(diào)用 get() 后又調(diào)用了 remove(),則可能再次調(diào)用此方法。
protected T initialValue() {
return null;
}
- public T get() 該方法返回當(dāng)前線程所對應(yīng)的線程局部變量。
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();
}
- public void remove() 將當(dāng)前線程局部變量的值刪除,目的是為了減少內(nèi)存的占用,該方法是JDK 5.0新增的方法。其實在線程結(jié)束后對應(yīng)的線程變量會自動被GC,主動調(diào)用這個方法目的是盡早回收,釋放內(nèi)存.
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
源碼淺析
也許我們分析JDK中的源碼有些難以理解,我們可以將ThreadLocal類簡化一下,簡化后的代碼如下
class MyThreadLocal<T>{
private Map<Thread,T> map = new HashMap<Thread,T>();
protected T initialValue() {
return null;
}
public void set(T value) {
map.put(Thread.currentThread(), value);
}
public void remove() {
map.remove(Thread.currentThread());
}
public T get() {
return map.get(Thread.currentThread());
}
}
其實在ThreadLocal內(nèi)部是維護了一個Map來保存數(shù)據(jù)的,雖然使用的時候獲取的是value值,但是其每一個value對應(yīng)的key都是當(dāng)前線程,因此相當(dāng)于將變量給每一個線程復(fù)制了一份,各個線程對數(shù)據(jù)的操作不會互相影響.
實用案例
我們在對數(shù)據(jù)庫進行操作的時候有時候為了數(shù)據(jù)安全必須加事務(wù)操作,但是一般事務(wù)管理在Service層,而數(shù)據(jù)操作在DAO層,一般的做法就是在Service層建立連接然后傳參的形式傳遞給DAO層,這無疑增加了程序的侵入性,其實在Spring的事務(wù)管理底層就使用了ThreadLocal來解決這種情況,我們也可以模擬使用ThreadLocal編寫一個帶有事務(wù)操作工具類.
public class JdbcUtils {
private static DataSource dataSource = new ComboPooledDataSource();
private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
//獲取數(shù)據(jù)源
public static DataSource getDataSource() {
return dataSource;
}
//獲取連接
public static Connection getConnection() throws SQLException {
Connection con = tl.get();
if(con == null) {
return dataSource.getConnection();
}
return con;
}
//開始事務(wù)
public static void beginTranscation() throws SQLException {
Connection con = tl.get();
if(con != null ) {
throw new SQLException("事務(wù)已經(jīng)開啟,在沒有結(jié)束當(dāng)前事務(wù)時,不能再開啟事務(wù)!");
}
con = dataSource.getConnection();
con.setAutoCommit(false);
tl.set(con);
}
//提交事務(wù)
public static void commitTransaction() throws SQLException {
Connection con = tl.get();
if(con == null ) {
throw new SQLException("當(dāng)前沒有事務(wù),所以不能提交事務(wù)!");
}
con.commit();
con.close();
tl.remove();
}
// 回滾事務(wù)
public static void rollbackTransaction() throws SQLException {
Connection con = tl.get();
if(con == null) {
throw new SQLException("當(dāng)前沒有事務(wù),所以不能回滾事務(wù)!");
}
con.rollback();
con.close();
tl.remove();
}
}