一、對ThreadLocal理解
ThreadLocal提供一個方便的方式,可以根據不同的線程存放一些不同的特徵屬性,可以方便的在線程中進行存取。
二、以session爲例來理解ThreadLocal
在web開發的session中,不同的線程對應不同的session,那麼如何針對不同的線程獲取對應的session呢?
我們可以設想了如下兩種方式:
1.在action中創建session,然後傳遞給Service,Service再傳遞給Dao,很明顯,這種方式將使代碼變得臃腫複雜。
2.創建一個靜態的map,鍵對應我們的線程,值對應session,當我們想獲取session時,只需要獲取map,然後根據當前的線程就可以獲取對應的值。
我們看看Hibernate中是如何實現這種情況的:
在Hibernate中是通過使用ThreadLocal來實現的。在getSession方法中,如果ThreadLocal存在session,則返回session,否則創建一個session放入ThreadLocal中。
總結一下就是在ThreadLocal中存放了一個session。
爲什麼我們在ThreadLocal存放一個session,這個session就會與一個線程對應呢?
實際上ThreadLocal中並沒有存放任何的對象或引用,在上面的的代碼中ThreadLocal的實例threadSession只相當於一個標記的作用。而存放對象的真正位置是正在運行的Thread線程對象,每個Thread對象中都存放着一個ThreadLocalMap類型threadLocals對象,這是一個映射表map,這個map的鍵是一個ThreadLocal對象,值就是我們想存的局部對象。
我們以上面的代碼爲例分析一下:
當我們往ThreadLocal中存放變量的時候發生了什麼?
即這行代碼時。
我們看下ThreadLocal的源碼中set()方法的實現。
如果把這些代碼簡化的話就一句
Thread.currentThread().threadLocals.set(this,value);
Thread.currentThread()獲取當前的線程
threadLocals就是我們上面說的每個線程對象中用於存放局部對象的map
所以set()就是獲取到當前線程的map然後把值放進去,我們發現鍵是this,也就是當前的ThreadLocal對象,可以發現ThreadLocal對象就是一個標記的作用,我們根據這個標記找到對應的局部對象。
如果對比get()方法,可以發現原理都差不多,都是對線程中的threadLocals這個map的操作,我就不解釋了。
ThreadLocal就是一個標記的作用,當我們在線程中使用ThreadLocal的set()或者get()方法時,其實是在操作我們線程自帶的threadLocals這個map,多個線程的時候自然就有多個map,這些map互相獨立,但是,這些map都是根據一個ThreadLocal對象(因爲它是靜態的)來作爲鍵存放。
這樣可以在多個線程中,每個線程存放不一樣的變量,我們通過一個ThreadLocal對象,在不同的線程(通過Thread.currentThread()獲取當前線程)中得到不同的值(不同線程的threadLocals不一樣)。
爲什麼threadLocals要是一個map呢?
因爲我們可能會在一個類中聲明多個ThreadLocal的實例,這樣就有多個標記,所以要使用map對應。
總結:
ThreadLocal就是用來在類中聲明的一個標記,然後通過這個標記就根據不同Thread對象存取值。
應用場景:
在線程中存放一些就像session的這種特徵變量,會針對不同的線程,有不同的值。
舉個栗子:
ThreadLocal:用於實現線程內部的數據共享叫線程共享(對於同一個線程內部數據一致),即相同的一段代碼 多個線程來執行 ,每個線程使用的數據只與當前線程有關。
實現原理:ThreadLocal相當於一個map 當前線程 存儲當前的變量的時候 map.put(確定線程的唯一值(比如變量名稱),變量),然後獲取的時候直接拿過來就行
一般用法:定義一個全局變量ThreadLoacl t 將新建線程要使用的變量 存進去 比如
1.當存儲的爲基本變量或者包裝對象時
package com.yanghs.test.traditional;
/**
* @author yanghs
* @Description:
* @date 2018/3/31 16:24
*/
public class ThreadLocalTest {
/*定義一個全局變量 來存放線程需要的變量*/
public static ThreadLocal<Integer> ti = new ThreadLocal<Integer>();
public static void main(String[] args) {
/*創建兩個線程*/
for(int i=0; i<2;i++){
new Thread(new Runnable() {
@Override
public void run() {
Double d = Math.random()*10;
/*存入當前線程獨有的值*/
ti.set(d.intValue());
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
/*取得當前線程所需要的值*/
System.out.println(ti.get());
}
}
static class B{
public void get(){
/*取得當前線程所需要的值*/
System.out.println(ti.get());
}
}
}
2.當存儲的爲對象時 就是數據集合 比如前臺傳過來的參數,每一個人傳過來的 都是這個人獨有的,才能保證數據準確性,抽取業務數據爲一個對象
class ThreadLocalDemo{
/*把線程相關的部分內聚到 類裏面 相當於map 每個類是對應key*/
private static ThreadLocal<ThreadLocalDemo> t = new ThreadLocal<ThreadLocalDemo>();
private ThreadLocalDemo(){}
public static ThreadLocalDemo getThreadInstance(){
ThreadLocalDemo threadLocalDemo = t.get();
if(null == threadLocalDemo){//當前線程無綁定的對象時,直接綁定一個新的對象
threadLocalDemo = new ThreadLocalDemo();
t.set(threadLocalDemo);
}
return threadLocalDemo;
}
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
把ThreadLocal 放在業務對象裏面提現高內聚,實現的目的是每一個線程都有一個獨立的ThreadLocalDemo對象。 使用的時候只需要 ThreadLocalDemo.getInstance()就可以得到當前線程的所需要的值。
package com.yanghs.test.traditional;
/**
* @author yanghs
* @Description:
* @date 2018/3/31 16:24
*/
public class ThreadLocalTest {
public static void main(String[] args) {
for(int i=0; i<2;i++){
new Thread(new Runnable() {
@Override
public void run() {
Double d = Math.random()*10;
ThreadLocalDemo.getThreadInstance().setName("name"+d);
new A().get();
new B().get();
}
}).start();
}
}
static class A{
public void get(){
System.out.println(ThreadLocalDemo.getThreadInstance().getName());
}
}
static class B{
public void get(){
System.out.println(ThreadLocalDemo.getThreadInstance().getName());
}
}
}
其實Struts2的ActionContext就是使用這種方式綁定數據。
參考博客:http://www.iteye.com/topic/103804
https://blog.csdn.net/Ryice/article/details/79771226