1. 程式人生 > >ThreadLocal類基本使用

ThreadLocal類基本使用

在多執行緒場景中,有一個常用的類ThreadLocal,該類作為一個橋樑可以將執行緒要操作的全域性變數和執行緒本身繫結:

如下多執行緒測試程式:

package test.HimmaQ;

class Channel { //訊息傳送通道
    private  static Message message;
    private Channel(){};
    public static void setMessage(Message m){
        message = m;
    }
    public static void send(){//傳送訊息
        System.out.println(message.getInfo());
    }

}
class Message { private String info; public void setInfo(String info) { this.info = info; } public String getInfo(){ return info; } } public class threadLocalTest { public static void main(String[] args) { new Thread(()->{ Message msg = new
Message(); msg.setInfo("第一個執行緒傳送訊息"); Channel.setMessage(msg); Channel.send(); }).start(); new Thread(()->{ Message msg = new Message(); msg.setInfo("第二個執行緒傳送訊息"); Channel.setMessage(msg); Channel.send(); }).start();
new Thread(()->{ Message msg = new Message(); msg.setInfo("第三個執行緒傳送訊息"); Channel.setMessage(msg); Channel.send(); }).start(); } }

執行結果:

第三個執行緒傳送訊息
第三個執行緒傳送訊息
第三個執行緒傳送訊息

Process finished with exit code 0

可以看到,連續3個執行緒執行輸出,輸出結果與預期結果不符;

原因分析:對訊息傳送通道Channel類,其屬性message是靜態全域性的,因此每一個執行緒過來都會操作該變數,當第一個執行緒為該物件設定了值還沒有發出時,第二個執行緒又重新給它設定了值,同樣的,第三個執行緒會覆蓋前兩個執行緒設定的值,因此在電腦速度快的情況下,我們只看到了第三個執行緒的輸出結果。即,多執行緒對全域性變數操作的情況下,發生了執行緒不安全(全域性變數的值的覆蓋)。

ThreadLoacl類

public class ThreadLocal<T>
extends Object

返回值 方法 說明
T get() 返回當前執行緒本地變數的當前執行緒的副本中的值。
void remove() 移除此執行緒區域性變數的當前執行緒的值。
void set(T value) 將此執行緒區域性變數的當前執行緒的副本設定為指定的值。
 

該類提供執行緒區域性變數。通過其get和set方法,可以將執行緒要操作的全域性變數的值與當前執行緒繫結。ThreadLocal例項通常是私有的靜態欄位,用在需要關聯執行緒的時候,比如將使用者id和當前執行緒繫結時候,可以將ID設定為ThreadLocal類。

可以將ThreadLocal類變數理解為一個map,其中存放的是每個執行緒與其對應的全域性變數副本的鍵值對,線上程使用的時候,拿到的並不是全域性變數本身,而是執行緒對應的全域性變數的副本,這樣就實現了執行緒和變數的繫結。

解決方案:

在Channel類中引入私有的全域性ThreadLocal類變數,用來將執行緒和需要操作的message關聯起來:

package test.HimmaQ;

class Channel { //訊息傳送通道
    private  static final ThreadLocal<Message> THREADLOCAL = new ThreadLocal<Message>();
    private Channel(){};
    public static void setMessage(Message m){
        THREADLOCAL.set(m);
    }
    public static void send(){//傳送訊息
        System.out.println(THREADLOCAL.get().getInfo());
    }

}
class Message {
    private String info;
    public void setInfo(String info) {
        this.info = info;
    }
    public String getInfo(){
        return info;
    }
}

public class threadLocalTest {
    public static void main(String[] args) {
        new Thread(()->{
            Message msg = new Message();
            msg.setInfo("第一個執行緒傳送訊息");
            Channel.setMessage(msg);
            Channel.send();
        }).start();
        new Thread(()->{
            Message msg = new Message();
            msg.setInfo("第二個執行緒傳送訊息");
            Channel.setMessage(msg);
            Channel.send();
        }).start();
        new Thread(()->{
            Message msg = new Message();
            msg.setInfo("第三個執行緒傳送訊息");
            Channel.setMessage(msg);
            Channel.send();
        }).start();
    }
}

執行結果:

第一個執行緒傳送訊息
第二個執行緒傳送訊息
第三個執行緒傳送訊息

Process finished with exit code 0