詳細的ThreadLocal以及與synchronized的區別
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可以使程式擁有更高的併發性,能夠保證程式執行的效率