ThreadLocal
是一個線程變量工具類。主要用於將私有線程和該線程存放的副本對象作一個映射,各個線程之間的變量互不干擾,例如A線程和B線程都想使用一個變量,此時就存在強資源的問題,而這個變量他能夠有多份,此時就能夠用ThreadLocal
做爲變量的管理者 ,不存在多線程隱患. 同時他隱式的能夠做爲一個線程內部的傳遞信息的一個工具 .java
ThreadLocal
從另外一個角度來解決線程的併發訪問。ThreadLocal
會爲每個線程提供一個獨立的變量副本,從而隔離了多個線程對數據的訪問衝突。例如在多線程中都去使用一個對象,可是又但願互不干涉,此時就須要用到ThreadLocal
.編程歸納起來講,對於多線程資源共享的問題,同步機制採用了「以時間換空間」的方式,而
ThreadLocal
採用了「以空間換時間」的方式。前者僅提供一份變量,讓不一樣的線程排隊訪問,然後者爲每個線程都提供了一份變量,所以能夠同時訪問而互不影響。多線程
咱們能夠發現 每個 線程 都有一個 map對象 , map存放的是本地線程對象和副本變量 .而後這個map對象由一個 threadlocal對象 維護 ,他負責去添加和維護併發
因此對於不一樣的線程,每次獲取副本值時,別的線程並不能獲取到當前線程的副本值,造成了副本的隔離,互不干擾。less
ThreadLocal values與這個線程有關,這個map被這個thread所維護ide
public class Thread implements Runnable {
/* ThreadLocal values pertaining(屬於) to this thread. This map is maintained(被..維護) * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
複製代碼
1 . public T get() 獲取當前線程的副本變量值。工具
2 . public void set(T value) 保存當前線程的副本變量值。this
3 . public void remove() 移除當前前程的副本變量值。spa
4 . public static ThreadLocal withInitial(Supplier supplier) 初始化變量線程
Returns the value in the current thread's copy of this thread-local variable. If the variable has no value for the current thread, it is first initialized to the value returned by an invocation of the initialValue method.
返回當前線程的線程局部變量副本中的值。若是變量沒有當前線程的值,則首先將其初始化爲調用initialValue方法返回的值。
Sets the current thread's copy of this thread-local variable to the specified value. Most subclasses will have no need to override this method, relying solely on the initialValue method to set the values of thread-locals.
將此線程局部變量的當前線程副本設置爲指定的值。大多數子類不須要重寫這個方法,只須要依賴initialValue方法來設置線程局部變量的值。
Removes the current thread's value for this thread-local variable. If this thread-local variable is subsequently read by the current thread, its value will be reinitialized by invoking its initialValue method, unless its value is set by the current thread in the interim. This may result in multiple invocations of the initialValue method in the current thread.
1 . 刪除此線程中的局部變量。若是這個線程局部變量隨後又被調用,那麼它的值將經過調用其initialValue方法從新初始化,除非它的值是由當前線程在過渡期間設置的。這可能致使在當前線程中屢次調用initialValue方法。
2 . 還要注意一點就是 不remove 會發生內存泄漏,溢出,看你線程消耗吧,固有線程隨意了,
Creates a thread local variable. The initial value of the variable is determined by invoking the get method on the Supplier.
創造一個線程的局部變量 , 經過調用get方法來肯定變量的初始值。
public class TestThreadLocal2 {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
public static Connection getConnection(){
Connection connection = threadLocal.get();
if (null == connection) {
connection = Datasource.get();
threadLocal.set(connection);
}
return connection;
}
public static void remove(){
threadLocal.remove();
}
public static void main(String[] args) throws InterruptedException {
Executor executors = Executors.newFixedThreadPool(10);
executors.execute(()->{
System.out.println(getConnection());
});
executors.execute(()->{
System.out.println(getConnection());
});
System.out.println(getConnection());
}
private static class Connection{
String name;
public Connection(String name) {
this.name = name;
}
}
private static class Datasource{
static ArrayBlockingQueue<Connection> connections = new ArrayBlockingQueue<Connection>(5);
static {
connections.offer(new Connection("1"));
connections.offer(new Connection("2"));
connections.offer(new Connection("3"));
connections.offer(new Connection("4"));
connections.offer(new Connection("5"));
}
static Connection get(){
Connection connection =null;
try {
// 移除並返回頭部元素 阻塞操做
connection = connections.take();
// 把移除的元素再添加回去 好比 一開始是 1 2 3 4 5 如今 take一個變成了 2 3 4 5 _ 而後put變成了 2 3 4 5 1 .
connections.put(connection);
} catch (InterruptedException e) {
System.out.println("線程阻塞");
}
return connection;
}
}
}
複製代碼
public class MainTest {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1000);
// 使用 ThreadLocal
Bank bank = new Bank();
IntStream.range(0, 3).forEach(e->{
pool.execute(()->{
bank.deposit(200,"新員工 : "+e);
});
});
pool.shutdown();
}
}
class Bank {
// 初始化帳戶餘額爲 1000
ThreadLocal<Integer> account = ThreadLocal.withInitial(() -> 1000);
public void deposit(int money,String name) {
System.out.println(name + "--當前帳戶餘額爲:" + account.get());
account.set(account.get() + money);
System.out.println(name + "--存入 " + money + " 後帳戶餘額爲:" + account.get());
account.remove();
}
}
複製代碼
輸出結果 :
新員工 : 0--當前帳戶餘額爲:1000
新員工 : 0--存入 200 後帳戶餘額爲:1200
新員工 : 2--當前帳戶餘額爲:1000
新員工 : 2--存入 200 後帳戶餘額爲:1200
新員工 : 1--當前帳戶餘額爲:1000
新員工 : 1--存入 200 後帳戶餘額爲:1200
複製代碼
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> sdf = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//
public static String format(Date date) {
String msg = null;
try {
// 調用get方法 -> withInitial初始化
msg = sdf.get().format(date);
} finally {
// 用完 remove
sdf.remove();
}
return msg;
}
public static String formatJava8(Date date){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
public static void main(String[] args) throws Exception {
System.err.println(format(new Date()));
System.err.println(formatJava8(new Date()));
}
}
複製代碼
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
// 只有key是弱類型引用,value並非 , 因此容易出現內存溢出的現象,若是不去手動remove的話
super(k);
value = v;
}
.....
}
複製代碼
因爲ThreadLocalMap的key是弱引用,而Value是強引用。這就致使了一個問題,ThreadLocal在沒有外部對象強引用時,發生GC時弱引用Key會被回收,而Value不會回收,若是建立ThreadLocal的線程一直持續運行,那麼這個Entry對象中的value就有可能一直得不到回收,發生內存泄露。
如何避免泄漏 既然Key是弱引用,那麼咱們要作的事,就是在調用ThreadLocal的get()、set()方法時完成後再調用remove方法,將Entry節點和Map的引用關係移除,這樣整個Entry對象在GC Roots分析後就變成不可達了,下次GC的時候就能夠被回收。
若是使用ThreadLocal的set方法以後,沒有顯示的調用remove方法,就有可能發生內存泄露,因此養成良好的編程習慣十分重要,使用完ThreadLocal以後,記得調用remove方法。