ThreadLocal的設計
首先看看ThreadLocal的接口:
Object get() ; // 返回當前線程的線程局部變量副本 protected Object
initialValue(); // 返回該線程局部變量的當前線程的初始值
void set(Object value); // 設置當前線程的線程局部變量副本的值
ThreadLocal有3個方法,其中值得注意的是initialValue(),該方法是一個protected
的方法,顯然是爲了子類重寫而特地實現的。該方法返回當前線程在該線程局部變量的初始
值,這個方法是一個延遲調用方法,在一個線程第1次調用get()或者set(Object)時才執行
,而且僅執行1次。ThreadLocal中的確實實現直接返回一個null:
protected Object initialValue() { return null; }
ThreadLocal是如何作到爲每個線程維護變量的副本的呢?其實實現的思路很簡單,
在ThreadLocal類中有一個Map,用於存儲每個線程的變量的副本。好比下面的示例實現:java
就至關於存Mapmysql
這個是ThreadLocal的get方法的實現 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(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocal與其它同步機制的比較
ThreadLocal和其它同步機制相比有什麼優點呢?ThreadLocal和其它全部的同步機制都
是爲了解決多線程中的對同一變量的訪問衝突,在普通的同步機制中,是經過對象加鎖來實
現多個線程對同一變量的安全訪問的。這時該變量是多個線程共享的,使用這種同步機制需
要很細緻地分析在何時對變量進行讀寫,何時須要鎖定某個對象,何時釋放該
對象的鎖等等不少。全部這些都是由於多個線程共享了資源形成的。ThreadLocal就從另外一
個角度來解決多線程的併發訪問,ThreadLocal會爲每個線程維護一個和該線程綁定的變
量的副本,從而隔離了多個線程的數據,每個線程都擁有本身的變量副本,從而也就沒有
必要對該變量進行同步了。ThreadLocal提供了線程安全的共享對象,在編寫多線程代碼時
,能夠把不安全的整個變量封裝進ThreadLocal,或者把該對象的特定於線程的狀態封裝進
ThreadLocal。
因爲ThreadLocal中能夠持有任何類型的對象,因此使用ThreadLocal get當前線程的值
是須要進行強制類型轉換。但隨着新的Java版本(1.5)將模版的引入,新的支持模版參數
的ThreadLocal<T>類將從中受益。也能夠減小強制類型轉換,並將一些錯誤檢查提早到了編
譯期,將必定程度地簡化ThreadLocal的使用。
總結
固然ThreadLocal並不能替代同步機制,二者面向的問題領域不一樣。同步機制是爲了同
步多個線程對相同資源的併發訪問,是爲了多個線程之間進行通訊的有效方式;而
ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量)
,這樣固然不須要對多個線程進行同步了。因此,若是你須要進行多個線程之間進行通訊,
則使用同步機制;若是須要隔離多個線程之間的共享衝突,可使用ThreadLocal,這將極
大地簡化你的程序,使程序更加易讀、簡潔。web
ThreadLocal常見用途:
存放當前session用戶
存放一些context變量,好比webwork的ActionContext
存放session,好比Spring hibernate orm的sessionsql
例子:用 ThreadLocal 實現每線程 Singleton
線程局部變量常被用來描繪有狀態「單子」(Singleton) 或線程安全的共享對象,或者是經過把不安全的整個變量封裝進 ThreadLocal,或者是經過把對象的特定於線程的狀態封裝進 ThreadLocal。例如,在與數據庫有緊密聯繫的應用程序中,程序的不少方法可能都須要訪問數據庫。在系統的每一個方法中都包含一個 Connection 做爲參數是不方便的 — 用「單子」來訪問鏈接多是一個雖然更粗糙,但卻方便得多的技術。然而,多個線程不能安全地共享一個 JDBC Connection。如清單 3 所示,經過使用「單子」中的 ThreadLocal,咱們就能讓咱們的程序中的任何類容易地獲取每線程 Connection 的一個引用。這樣,咱們能夠認爲 ThreadLocal 容許咱們建立每線程單子。
數據庫
package org.heinrich.app.connection; import java.sql.Connection; public class ConnectionUtils { private final static ThreadLocal<Connection> threadLocal = new ThreadLocal<>(); public Connection getConnection(){ Connection connection = threadLocal.get(); if(connection ==null){ connection = new DBHelper().getConn(); threadLocal.set(connection); } return connection; } } package org.heinrich.app.connection; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; //數據庫鏈接 public class DBHelper { public static final String url = "jdbc:mysql://localhost:3306/fk_test"; public static final String name = "com.mysql.jdbc.Driver"; public static final String user = "root"; public static final String password = "root"; public Connection conn = null; public Connection getConn() { try { Class.forName(name);// 指定鏈接類型 conn = DriverManager.getConnection(url, user, password);// 獲取鏈接 } catch (Exception e) { e.printStackTrace(); } return conn; } }
簡單的實現Mysql鏈接線程安全的一種方式緩存
理論上來講,ThreadLocal是的確是相對於每一個線程,每一個線程會有本身的ThreadLocal。可是上面已經講到,通常的應用服務器都會維護一套線程池。所以,不一樣用戶訪問,可能會接受到一樣的線程。所以,在作基於TheadLocal時,須要謹慎,避免出現ThreadLocal變量的緩存,致使其餘線程訪問到本線程變量。安全