ThreadLocal類深刻理解
synchronized這類線程同步的機制可以解決多線程并發問題,在這種解決方案下,多個線程訪問到的,都是同一份變量的內容。為了防止在多線程訪問的過程中,可能會出現的并發錯誤。不得不對多個線程的訪問進行同步,這樣也就意味著,多個線程必須先后對變量的值進行訪問或者修改,這是一種以延長訪問時間來換取線程安全性的策略。
而ThreadLocal類為每一個線程都維護了自己獨有的變量拷貝。每個線程都擁有了自己獨立的一個變量,競爭條件被徹底消除了,那就沒有任何必要對這些線程進行同步,它們也能最大限度的由CPU調度,并發執行。并且由于每個線程在訪問該變量時,讀取和修改的,都是自己獨有的那一份變量拷貝,變量被徹底封閉在每個訪問的線程中,并發錯誤出現的可能也完全消除了。對比前一種方案,這是一種以空間來換取線程安全性的策略。
主要方法如下:
try {
// 通過默認配置文件hibernate.cfg.xml創建SessionFactory
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
log.error("初始化SessionFactory失敗!", ex);
throw new ExceptionInInitializerError(ex);
}
}
//創建線程局部變量session,用來保存Hibernate的Session
public static final ThreadLocal session = new ThreadLocal();
* 獲取當前線程中的Session
* @return Session
* @throws HibernateException
*/
public static Session currentSession() throws HibernateException {
Session s = (Session) session.get();
// 如果Session還沒有打開,則新開一個Session
if (s == null) {
s = sessionFactory.openSession();
session.set(s); //將新開的Session保存到線程局部變量中
}
return s;
}
//獲取線程局部變量,并強制轉換為Session類型
Session s = (Session) session.get();
session.set(null);
if (s != null)
s.close();
}
}
* 學生
*/
public class Student {
private int age = 0; //年齡
return this.age;
}
this.age = age;
}
}
* 多線程下測試程序
*/
public class ThreadLocalDemo implements Runnable {
//創建線程局部變量studentLocal,在后面你會發現用來保存Student對象
private final static ThreadLocal studentLocal = new ThreadLocal();
ThreadLocalDemo td = new ThreadLocalDemo();
Thread t1 = new Thread(td, "a");
Thread t2 = new Thread(td, "b");
t1.start();
t2.start();
}
accessStudent();
}
* 示例業務方法,用來測試
*/
public void accessStudent() {
//獲取當前線程的名字
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running!");
Random random = new Random();
int age = random.nextInt(100);
System.out.println("thread " + currentThreadName + " set age to:" + age);
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());
}
//獲取本地線程變量并強制轉換為Student類型
Student student = (Student) studentLocal.get();
//線程首次執行此方法的時候,studentLocal.get()肯定為null
if (student == null) {
//創建一個Student對象,并保存到本地線程變量studentLocal中
student = new Student();
studentLocal.set(student);
}
return student;
}
}
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
3、數據庫連接管理
public class ConnectionManager { |
06 |
07 |
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { |
08 |
@Override |
09 |
protected Connection initialValue() { |
10 |
Connection conn = null ; |
11 |
try { |
12 |
conn = DriverManager.getConnection( |
13 |
"jdbc:mysql://localhost:3306/test" , "username" , |
14 |
"password" ); |
15 |
} catch (SQLException e) { |
16 |
e.printStackTrace(); |
17 |
} |
18 |
return conn; |
19 |
} |
20 |
}; |
21 |
22 |
public static Connection getConnection() { |
23 |
return connectionHolder.get(); |
24 |
} |
25 |
26 |
public static void setConnection(Connection conn) { |
27 |
connectionHolder.set(conn); |
28 |
} |
29 |
} |
知其所以然
那么到底ThreadLocal類是如何實現這種“為每個線程提供不同的變量拷貝”的呢?先來看一下ThreadLocal的set()方法的源碼是如何實現的:
01 |
/** |
02 |
* Sets the current thread's copy of this thread-local variable |
03 |
* to the specified value. Most subclasses will have no need to |
04 |
* override this method, relying solely on the {@link #initialValue} |
05 |
* method to set the values of thread-locals. |
06 |
* |
07 |
* @param value the value to be stored in the current thread's copy of |
08 |
* this thread-local. |
09 |
*/ |
10 |
public void set(T value) { |
11 |
Thread t = Thread.currentThread(); |
12 |
ThreadLocalMap map = getMap(t); |
13 |
if (map != null ) |
14 |
map.set( this , value); |
15 |
else |
16 |
createMap(t, value); |
17 |
} |
沒有什么魔法,在這個方法內部我們看到,首先通過getMap(Thread t)方法獲取一個和當前線程相關的ThreadLocalMap,然后將變量的值設置到這個ThreadLocalMap對象中,當然如果獲取到的 ThreadLocalMap對象為空,就通過createMap方法創建。
線程隔離的秘密,就在于ThreadLocalMap這個類。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取(對比Map對象來理解),每個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。 ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不同線程中的隔離。因為每個線程的變量都是自己特有的,完全不會有并發錯誤。還有一點就是,ThreadLocalMap存儲的鍵值對中的鍵是this對象指向的ThreadLocal對象,而值就是你所設置的對象了。
為了加深理解,我們接著看上面代碼中出現的getMap和createMap方法的實現:
1 |
ThreadLocalMap getMap(Thread t) { |
2 |
return t.threadLocals; |
3 |
} |
1 |
void createMap(Thread t, T firstValue) { |
2 |
t.threadLocals = new ThreadLocalMap( this , firstValue); |
3 |
} |
代碼已經說的非常直白,就是獲取和設置Thread內的一個叫threadLocals的變量,而這個變量的類型就是ThreadLocalMap,這樣進一步驗證了上文中的觀點:每個線程都有自己獨立的ThreadLocalMap對象。打開java.lang.Thread類的源代碼,我們能得到更直觀的證明:
1 |
/* ThreadLocal values pertaining to this thread. This map is maintained |
2 |
* by the ThreadLocal class. */ |
3 |
ThreadLocal.ThreadLocalMap threadLocals = null ; |
那么接下來再看一下ThreadLocal類中的get()方法,代碼是這么說的:
01 |
/** |
02 |
* Returns the value in the current thread's copy of this |
03 |
* thread-local variable. If the variable has no value for the |
04 |
* current thread, it is first initialized to the value returned |
05 |
* by an invocation of the {@link #initialValue} method. |
06 |
* |
07 |
* @return the current thread's value of this thread-local |
08 |
*/ |
09 |
public T get() { |
10 |
Thread t = Thread.currentThread(); |
11 |
ThreadLocalMap map = getMap(t); |
12 |
if (map != null ) { |
13 |
ThreadLocalMap.Entry e = map.getEntry( this ); |
14 |
if (e != null ) |
15 |
return (T)e.value; |
16 |
} |
17 |
return setInitialValue(); |
18 |
} |
19 |
20 |
/** |
21 |
* Variant of set() to establish initialValue. Used instead |
22 |
* of set() in case user has overridden the set() method. |
23 |
* |
24 |
* @return the initial value |
25 |
*/ |
26 |
private T setInitialValue() { |
27 |
T value = initialValue(); |
28 |
Thread t = Thread.currentThread(); |
29 |
ThreadLocalMap map = getMap(t); |
30 |
if (map != null ) |
31 |
map.set( this , value); |
32 |
else |
33 |
createMap(t, value); |
34 |
return value; |
35 |
} |
這兩個方法的代碼告訴我們,在獲取和當前線程綁定的值時,ThreadLocalMap對象是以this指向的ThreadLocal對象為鍵進行查找的,這當然和前面set()方法的代碼是相呼應的。
進一步地,我們可以創建不同的ThreadLocal實例來實現多個變量在不同線程間的訪問隔離,為什么可以這么做?因為不同的ThreadLocal對象作為不同鍵,當然也可以在線程的ThreadLocalMap對象中設置不同的值了。通過ThreadLocal對象,在多線程中共享一個值和多個值的區別,就像你在一個HashMap對象中存儲一個鍵值對和多個鍵值對一樣,僅此而已。
設置到這些線程中的隔離變量,會不會導致內存泄漏呢?ThreadLocalMap對象保存在Thread對象中,當某個線程終止后,存儲在其中的線程隔離的變量,也將作為Thread實例的垃圾被回收掉,所以完全不用擔心內存泄漏的問題。在多個線程中隔離的變量,光榮的生,合理的死,真是圓滿,不是么?
最后再提一句,ThreadLocal變量的這種隔離策略,也不是任何情況下都能使用的。如果多個線程并發訪問的對象實例只允許,也只能創建那么一個,那就沒有別的辦法了,老老實實的使用同步機制來訪問吧。
總結:
ThreadLocal使用的一般步驟