why:爲什麼要用ThreadLocal
示例:不適用ThreadLocal共同使用變量,使用static,每個值修改同一個變量,產生錯誤
/**
* 不使用ThreadLocal共同使用變量,使用static,每個值修改同一個變量,產生錯誤
* @author: honry.guan
* @create: 2020-06-07 18:53
**/
public class NoThreadLocalTest extends Thread{
private static int num = 1;
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+": 開始執行,num = "+num);
num = num + 1;
System.out.println(Thread.currentThread().getName()+" 結束,num = "+num);
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
NoThreadLocalTest t = new NoThreadLocalTest();
t.start();
}
}
}
運行結果,可能有幾種可能性:
Thread-1: 開始執行,num = 1
Thread-0: 開始執行,num = 1
Thread-0 結束,num = 3
Thread-2: 開始執行,num = 1
Thread-1 結束,num = 2
Thread-2 結束,num = 4
也有可能是:
Thread-1: 開始執行,num = 1
Thread-2: 開始執行,num = 1
Thread-2 結束,num = 2
Thread-0: 開始執行,num = 1
Thread-0 結束,num = 4
Thread-1 結束,num = 4
使用ThreadLocal
public class NoThreadLocalTest extends Thread{
private ThreadLocal<Integer> num = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+": 開始執行,num = "+num.get());
num.set(num.get() + 1);
System.out.println(Thread.currentThread().getName()+" 結束,num = "+num.get());
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
NoThreadLocalTest t = new NoThreadLocalTest();
t.start();
}
}
}
結果:都是2
Thread-0: 開始執行,num = 1
Thread-2: 開始執行,num = 1
Thread-2 結束,num = 2
Thread-1: 開始執行,num = 1
Thread-0 結束,num = 2
Thread-1 結束,num = 2
ThreadLocal的使用
ThreadLocal類接口很簡單,常用的有4個方法
• void set(Object value):設置當前線程的線程局部變量的值。
• public Object get():該方法返回當前線程所對應的線程局部變量。
• public void remove():移除變量,如果最開始是1,通過set修改之後,調用remove之後,get出來的值又變成1
• protected Object initialValue():初始,第一次調用set或get才執行,並且僅執行一次。
remove的測試
public class NoThreadLocalTest extends Thread{
private ThreadLocal<Integer> num = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+": 開始執行,num = "+num.get());
num.set(num.get() + 1);
System.out.println(Thread.currentThread().getName()+" 結束,num = "+num.get());
num.remove();
System.out.println(Thread.currentThread().getName()+" 結束remove之後,num = "+num.get());
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
NoThreadLocalTest t = new NoThreadLocalTest();
t.start();
}
}
}
執行結果
Thread-1: 開始執行,num = 1
Thread-2: 開始執行,num = 1
Thread-0: 開始執行,num = 1
Thread-0 結束,num = 2
Thread-2 結束,num = 2
Thread-1 結束,num = 2
Thread-1 結束remove之後,num = 1
Thread-2 結束remove之後,num = 1
Thread-0 結束remove之後,num = 1
ThreadLocal初始化方法的使用
1、實現方法initialValue
private ThreadLocal<Integer> num = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
2、通過lomoba表達式
ThreadLocal<Integer> num = ThreadLocal.withInitial(()->1);
ThreadLocal在線程中的使用,即怎麼讓多個線程使用threadLocal
(我剛學的時候一直被這個事情困擾,自己手寫幾次之後明白)
只要在線程run方法中,調用ThreadLocal的get或者set,不管是線程對象內部自定義threadLocal,還是通過構造函數傳遞進線程的ThreadLocal變量。
1、線程對象內部自定義threadLocal
public class NoThreadLocalTest extends Thread{
private ThreadLocal<Integer> num = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+": 開始執行,num = "+num.get());
num.set(num.get() + 1);
System.out.println(Thread.currentThread().getName()+" 結束,num = "+num.get());
num.remove();
System.out.println(Thread.currentThread().getName()+" 結束remove之後,num = "+num.get());
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
NoThreadLocalTest t = new NoThreadLocalTest();
t.start();
}
}
}
通過構造函數傳遞進線程的ThreadLocal變量
public class NoThreadLocalTest extends Thread{
private ThreadLocal<Integer> num ;
public NoThreadLocalTest(ThreadLocal<Integer> num) {
this.num = num;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+": 開始執行,num = "+num.get());
num.set(num.get() + 1);
System.out.println(Thread.currentThread().getName()+" 結束,num = "+num.get());
num.remove();
System.out.println(Thread.currentThread().getName()+" 結束remove之後,num = "+num.get());
}
public static void main(String[] args) {
ThreadLocal<Integer> num = ThreadLocal.withInitial(()->1);
}
}
此時不論什麼一個線程能夠併發訪問這個變量,對它進行寫入、讀取操作,都是線程安全的。