ThreadLocal
概述
threadlocal是一個線程內部的存儲類,可以在指定線程內存儲數據,數據存儲以后,只有指定線程可以得到存儲數據
ThreadLocal提供了線程內存儲變量的能力,這些變量不同之處在於每一個線程讀取的變量是對應的互相獨立的。通過get和set方法就可以得到當前線程對應的值。
做個不恰當的比喻,從表面上看ThreadLocal相當於維護了一個map,key就是當前的線程,value就是需要存儲的對象。
這里的這個比喻是不恰當的,實際上是ThreadLocal的靜態內部類ThreadLocalMap為每個Thread都維護了一個數組table,ThreadLocal確定了一個數組下標,而這個下標就是value存儲的對應位置。
ThreadLocal類用來提供線程內部的局部變量。這種變量在多線程環境下訪問(通過get和set方法訪問)時能保證各個線程的變量相對獨立於其他線程內的變量。
ThreadLocal實例通常來說都是private static類型的,用於關聯線程和線程上下文
作用
提供線程內的局部變量,不同的線程之間不會相互干擾,這種變量在線程的生命周期內起作用
總結:
1.線程並發:在多線程並發的場景下(單線程是用不到ThreadLocal的)
2.傳遞數據:我們可以通過ThreadLocal在同一線程,不同組件中傳遞公共變量(和域對象有點相似)
3.線程隔離:每個線程的變量都是獨立的不會互相影響(ThreadLocal的核心)
基本使用
常用方法
方法聲名 | 描述 |
---|---|
ThreadLocal( ) | 創建ThreadLocal對象 |
public void set(T value) | 設置當前線程綁定的局部變量 |
public T get( ) | 獲取當前線程綁定的局部變量 |
public void remove( ) | 移除當前線程綁定的局部變量 |
使用案例
演示問題:
/*
需求:線程隔離
在多線程並發的場景下,每個線程中的變量都是相互獨立
線程A: 設置(變量1) 獲取(變量1)
線程B: 設置(變量2) 獲取(變量2)
*/
public class MyDemo01 {
//變量
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) {
MyDemo01 demo = new MyDemo01();
//開啟五個線程
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//每個線程:存一個變量,過一會取出這個變量
demo.setContent(Thread.currentThread().getName() + "的數據");
System.out.println("-----------------");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}
});
//設置線程名字
thread.setName("線程"+i);
thread.start();
}
}
}
結果顯示:
多運行幾次,就會出現以上數據,每個線程存入的數據和取出的數據是不一致的,java中的線程調度是搶占式調度,本身具備隨機性
這種情況就是線程不隔離
應該怎么解決呢?
利用ThreadLocal
ThreadLocal
使用ThreadLocal改進之后的代碼
/*
需求:線程隔離
在多線程並發的場景下,每個線程中的變量都是相互獨立
線程A: 設置(變量1) 獲取(變量1)
線程B: 設置(變量2) 獲取(變量2)
ThreadLocal:
1.set(): 將變量綁定到當前線程中
2.get(): 獲取當前線程綁定的變量
*/
public class MyDemo01 {
ThreadLocal<String> t1 = new ThreadLocal<>();
//變量
private String content;
public String getContent() {
// return content;
String s = t1.get();
return s;
}
public void setContent(String content) {
// this.content = content;
//將變量content綁定到當前線程
t1.set(content);
}
public static void main(String[] args) {
MyDemo01 demo = new MyDemo01();
//開啟五個線程
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//每個線程:存一個變量,過一會取出這個變量
demo.setContent(Thread.currentThread().getName() + "的數據");
System.out.println("-----------------");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}
});
//設置線程名字
thread.setName("線程"+i);
thread.start();
}
}
}
再次運行,多運行幾次,增加出現問題的幾率
結果正常,哪怕加上thread.sleep(200); 結果也還是正常的
ThreadLocal解決了線程隔離的這個需求
與 synchronized 的區別
使用synchronized也可以完成隔離線程的需求
public class MyDemo02 {
//變量
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static void main(String[] args) throws InterruptedException {
MyDemo02 demo = new MyDemo02();
//開啟五個線程
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//每個線程:存一個變量,過一會取出這個變量
//使用synchronized代碼塊來完成需求
synchronized (MyDemo02.class){
demo.setContent(Thread.currentThread().getName() + "的數據");
System.out.println("-----------------");
System.out.println(Thread.currentThread().getName() + "--->" + demo.getContent());
}
}
});
//設置線程名字
thread.setName("線程"+i);
thread.start();
thread.sleep(200);
}
}
}
那么ThreadLocal與synchronized的區別是什么呢?
右邊的代碼加了synchronized鎖,線程只能一個一個去執行,排隊進行訪問,效率低,讓我們的程序失去了並發性
左邊沒有加鎖,一樣可以並發執行
雖然ThreadLocal模式與synchronized關鍵字都用於處理多線程並發訪問變量的問題,
但是兩者處理問題的角度和思路不同
synchronized | ThreadLocal | |
---|---|---|
原理 | 同步機制采用“以時間換空間”的方式,只提供了一份變量,讓不同的線程排隊訪問 | ThreadLocal采用“以空間換空間”的方式,為每一個線程都提供了一份變量的副本,從而實現同時訪問而相不干擾 |
側重點 | 多個線程之間訪問資源的同步 | 多線程中讓每個線程之間的數據相互隔離 |
小結:
synchronized鎖是解決線程的同步問題,線程失去並發性,效率低
ThreadLocal可以使程序擁有更高的並發性,能夠保證程序執行的效率