Java多線程基礎(十三)——Thread-Specific Storage(ThreadLocal)模式

1、定義

Thread-Specific Storage就是「線程獨有的存儲庫」,該模式會對每一個線程提供獨有的內存空間。
java.lang.ThreadLocal類提供了該模式的實現,ThreadLocal的實例是一種集合(collection)架構,該實例管理了不少對象,能夠想象成一個保管有大量保險箱的房間。java

java.lang.ThreadLocal類的方法:安全

  • public void set()

該方法會檢查當前調用線程,默認以該線程的Thread.currentThread()值做爲鍵,來保存指定的值。數據結構

  • public Object get()

該方法會檢查當前調用線程,默認以該線程的Thread.currentThread()值做爲鍵,獲取保存指定的值。架構

2、模式案例

TSLog類:性能

//實際執行記錄日誌的類,每一個線程都會擁有一個該類的實例
public class TSLog {
    private PrintWriter writer = null;
    public TSLog(String filename) {
        try {
            writer = new PrintWriter(new FileWriter(filename));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void println(String s) {
        writer.println(s);
    }
    public void close() {
        writer.println("==== End of log ====");
        writer.close();
    }
}

Log類:this

public class Log {
    private static final ThreadLocal<TSLog> tsLogCollection = new ThreadLocal<TSLog>();
    public static void println(String s) {
        getTSLog().println(s);
    }
    public static void close() {
        getTSLog().close();
    }
    private static TSLog getTSLog() {
        TSLog tsLog = (TSLog) tsLogCollection.get();
        if (tsLog == null) {
            tsLog = new TSLog(Thread.currentThread().getName() + "-log.txt");
            tsLogCollection.set(tsLog);
        }
        return tsLog;
    }
}

ClientThread類:spa

public class ClientThread extends Thread {
    public ClientThread(String name) {
        super(name);
    }
    public void run() {
        System.out.println(getName() + " BEGIN");
        for (int i = 0; i < 10; i++) {
            Log.println("i = " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
        Log.close();
        System.out.println(getName() + " END");
    }
}

執行:
Alice、Bobby、Chris三個線程調用Log類的同一個方法,但實際上每一個線程都擁有獨自的TSLog實例。線程

public class Main {
    public static void main(String[] args) {
        new ClientThread("Alice").start();
        new ClientThread("Bobby").start();
        new ClientThread("Chris").start();
    }
}

3、模式講解

Thread-Specific Storage模式的角色以下:設計

  • Client(委託人)參與者

Client參與者會將工做委託給TSObjectProxy參與者。(案例中的ClientThread類就是Client)代理

  • TSObjectProxy(線程獨有對象的代理者)參與者

TSObjectProxy參與者會處理多個Client委託的工做。(案例中的Log類就是TSObjectProxy)

  • TSObjectCollection(線程獨有對象的集合)參與者

(案例中的java.lang.ThreadLocal類就是TSObjectCollection)

  • TSObject(線程獨有的對象)參與者

TSObject存放線程所特有的信息,TSObject實例的方法只會由單線程調用,由TSObjectCollection管理,每一個線程都擁有獨立的TSObject實例。(案例中的TSLog類就是TSObject)

4、ThreadLocal的原理

ThreadLocal類主要有四個方法:

一、初始化返回值的方法:
該方法實現只返回 null,而且修飾符爲protected,很明顯,若是用戶想返回初始值不爲null,則須要重寫該方法;

protected T initialValue() {
    return null;
}

二、get方法,獲取線程本地副本變量

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

三、set方法,設置線程本地副本變量

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

四、remove方法,移除線程本地副本變量

public void remove() {
 ThreadLocalMap m = getMap(Thread.currentThread());
 if (m != null)
     m.remove(this);
}

若是須要咱們本身來設計ThreadLocal對象,那麼,通常的實現思路:
設計一個線程安全的Map,key就是當前線程對象,Value就是線程本地變量的值。

然而,JDK的實現思路:

讓每一個Thread對象,自身持有一個Map,這個Map的Key就是當前ThreadLocal對象,Value是本地線程變量值。相對於加鎖的實現方式,這樣作能夠提高性能,實際上是一種以時間換空間的思路。

ThreadLocal類有個getMap()方法,其實就是返回Thread對象自身的Map——threadLocals。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

threadLocals是一種ThreadLocal.ThreadLocalMap類型的數據結構,做爲內部類定義在ThreadLocal類中,其內部採用一種WeakReference的方式保存鍵值對。

相關文章
相關標籤/搜索