ThreadLocal具體用法和適用場景

1.什麼是ThreadLocal程序員

       ThreadLocal是什麼呢?其實ThreadLocal並不是是一個線程的本地實現版本,它並非一個Thread,而是threadlocalvariable(線程局部變量)。也許把它命名爲ThreadLocalVar更加合適。線程局部變量(ThreadLocal)其實的功用很是簡單,就是爲每個使用該變量的線程都提供一個變量值的副本,是Java中一種較爲特殊的線程綁定機制,是每個線程均可以獨立地改變本身的副本,而不會和其它線程的副本衝突。從線程的角度看,每一個線程都保持一個對其線程局部變量副本的隱式引用,只要線程是活動的而且 ThreadLocal 實例是可訪問的;在線程消失以後,其線程局部實例的全部副本都會被垃圾回收(除非存在對這些副本的其餘引用)。spring

2.API說明安全

ThreadLocal()
          建立一個線程本地變量。
T get()
          返回此線程局部變量的當前線程副本中的值,若是這是線程第一次調用該方法,則建立並初始化此副本。
protected  T initialValue()
          返回此線程局部變量的當前線程的初始值。最多在每次訪問線程來得到每一個線程局部變量時調用此方法一次,即線程第一次使用 get() 方法訪問變量的時候。若是線程先於 get 方法調用 set(T) 方法,則不會在線程中再調用 initialValue 方法。
   若該實現只返回 null;若是程序員但願將線程局部變量初始化爲 null 之外的某個值,則必須爲 ThreadLocal 建立子類,並重寫此方法。一般,將使用匿名內部類。initialValue 的典型實現將調用一個適當的構造方法,並返回新構造的對象。
void remove()
          移除此線程局部變量的值。這可能有助於減小線程局部變量的存儲需求。若是再次訪問此線程局部變量,那麼在默認狀況下它將擁有其 initialValue。
void set(T value)
          將此線程局部變量的當前線程副本中的值設置爲指定值。許多應用程序不須要這項功能,它們只依賴於 initialValue() 方法來設置線程局部變量的值。
 
3.典型用例
一、Hiberante的Session 工具類HibernateUtil
這個類是Hibernate官方文檔中HibernateUtil類,用於session管理。
 
public class HibernateUtil {
    private static Log log = LogFactory.getLog(HibernateUtil.class);
    private static final SessionFactory sessionFactory;     //定義SessionFactory
 
    static {
        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;
    }
 
    public static void closeSession() throws HibernateException {
        //獲取線程局部變量,並強制轉換爲Session類型
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}
 
在這個類中,因爲沒有重寫ThreadLocal的initialValue()方法,則首次建立線程局部變量session其初始值爲null,第一次調用currentSession()的時候,線程局部變量的get()方法也爲null。所以,對session作了判斷,若是爲null,則新開一個Session,並保存到線程局部變量session中,這一步很是的關鍵,這也是「public static final ThreadLocal session = new ThreadLocal()」所建立對象session能強制轉換爲Hibernate Session對象的緣由。
 
二、另一個實例
建立一個Bean,經過不一樣的線程對象設置Bean屬性,保證各個線程Bean對象的獨立性。
 
/**
 * Created by IntelliJ IDEA.
 * User: leizhimin
 * Date: 2007-11-23
 * Time: 10:45:02
 * 學生
 */
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
 * 多線程下測試程序
 */
public class ThreadLocalDemo implements Runnable {
    //建立線程局部變量studentLocal,在後面你會發現用來保存Student對象
    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();
    }
 
    /**
     * 示例業務方法,用來測試
     */
    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 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() {
        //獲取本地線程變量並強制轉換爲Student類型
        Student student = (Student) studentLocal.get();
        //線程首次執行此方法的時候,studentLocal.get()確定爲null
        if (student == null) {
            //建立一個Student對象,並保存到本地線程變量studentLocal中
            student = new Student();
            studentLocal.set(student);
        }
        return student;
    }
}
 
運行結果:
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兩個線程age在不一樣時刻打印的值是徹底相同的。這個程序經過妙用ThreadLocal,既實現多線程併發,遊兼顧數據的安全性。
 
*spring裏面也常用localthread來處理事務,aop等業務因此說localThread很是具備研究必要,另外在實際使用中,用完localThread必定要手動清空對象,雖然localThread裏面保存的對象是WeakReference,可是隻針對key沒有針對value因此一不當心會有內存泄露風險必定要注意
相關文章
相關標籤/搜索