Java中的ThreadLocal

關於 ThreadLocal,咱們常常用它來解決多線程併發問題,那它到底是如何作到的?今天就讓咱們來好好看一下。 git

從源碼入手

首先,讓咱們看看 ThreadLocal 類中的介紹:github

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).session

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).多線程

按照文中所述,ThreadLocal 提供的是線程本地變量,每一個線程都有一份單獨的副本,常常使用的方式是私有靜態變量。關鍵在於下一段,線程存活,ThreadLocal 實例就能夠被訪問,線程消失,就會被垃圾回收。併發

get()方法

看到這兒,有沒有想起上一篇內容所說的引用類型,有多是軟引用或者弱引用,具體是什麼呢?仍是來看看代碼:app

public T get() {
        // 獲取當前線程
        Thread t = Thread.currentThread();
        // 獲取線程裏的map
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }複製代碼

上面展現的是 ThreadLocal 中的get()方法,關鍵的 map 是在 Thread 類中的threadLocals變量,讓咱們繼續看看 ThreadLocalMap 的源代碼:less

ThreadLocal.ThreadLocalMap threadLocals = null;

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                // 使用ThreadLocal做爲key,而且是弱引用
                super(k);
                value = v;
            }
        }

        // 省略代碼
    }複製代碼

根據上一篇文章所述,若是一個對象只有弱引用,那麼當下一次 GC 進行時,該對象就會被回收。那麼讓咱們整理一下:ide

  1. ThreadLocalMap 的 Entry 對 ThreadLocal 的引用爲弱引用
  2. ThreadLocal 自己並不存儲值,具體的 value 依舊在各個線程中。所以你能夠把 ThreadLocal 當作一個工具類。

但須要注意的是,Entry 中,只有key是弱引用,但 value 依舊是強引用。那會不會出現 key 被垃圾回收後,這個 map 的 key 爲 null,但 value 依舊存在的狀況呢?工具

set()方法

確實是有可能的,但 JDK 自己也作了優化,能夠看看 ThreadLocalMap 的 set()方法:優化

private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }複製代碼

調用 set()的時候,ThreadLocalMap 檢查到 key 爲 null 的 entry 時,會將 value 也設置爲 null,這樣 value 以前對應的實例也能夠被回收。

使用場景

簡單使用

先讓咱們看一個簡單的例子:

public class ThreadLocalSimpleDemo {

    public static void main(String[] args) {
        int threads = 3;
        InnerClass innerClass = new InnerClass();
        for (int i = 1; i <= threads; i++) {
            new Thread(() -> {
                for (int j = 0; j < 4; j++) {
                    innerClass.add(String.valueOf(j));
                    innerClass.print();
                }
                innerClass.set("hello world");
            }, "thread - " + i).start();
        }
    }

    private static class InnerClass {

        /**
         * 添加
         */
        public void add(String newStr) {
            StringBuilder str = Counter.counter.get();
            Counter.counter.set(str.append(newStr));
        }

        /**
         * 打印
         */
        public void print() {
            System.out.printf(
                    "Thread name:%s , ThreadLocal hashcode:%s, Instance hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    Counter.counter.hashCode(),
                    Counter.counter.get().hashCode(),
                    Counter.counter.get().toString()
            );
        }

        /**
         * 賦值
         */
        public void set(String words) {
            Counter.counter.set(new StringBuilder(words));
            System.out.printf(
                    "Set, Thread name:%s , ThreadLocal hashcode:%s,  Instance hashcode:%s, Value:%s\n",
                    Thread.currentThread().getName(),
                    Counter.counter.hashCode(),
                    Counter.counter.get().hashCode(),
                    Counter.counter.get().toString()
            );
        }
    }

    private static class Counter {
        /**
         * 初始化時是一個空的StringBuilder對象
         */
        private static ThreadLocal<StringBuilder> counter = ThreadLocal.withInitial(StringBuilder::new);
    }
}複製代碼

其打印結果爲:

Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:0
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:01
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:012
Thread name:thread - 2 , ThreadLocal hashcode:310471657, Instance hashcode:126253473, Value:0123
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:0
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:01
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:012
Thread name:thread - 1 , ThreadLocal hashcode:310471657, Instance hashcode:829132711, Value:0123
Set, Thread name:thread - 1 , ThreadLocal hashcode:310471657,  Instance hashcode:820066274, Value:hello world
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:01
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:012
Set, Thread name:thread - 2 , ThreadLocal hashcode:310471657,  Instance hashcode:155293473, Value:hello world
Thread name:thread - 3 , ThreadLocal hashcode:310471657, Instance hashcode:640658548, Value:0123
Set, Thread name:thread - 3 , ThreadLocal hashcode:310471657,  Instance hashcode:1804272849, Value:hello world複製代碼

能夠看出,咱們在使用 ThreadLocal 時,用的是同一個對象,但各個線程對應的實例是不同的。而在調用 set() 方法後,對應的實例會被替換。

Session

對於 Java Web 應用而言,Session 保存了不少信息。不少時候須要經過 Session 獲取信息,有些時候又須要修改 Session 的信息。一方面,須要保證每一個線程有本身單獨的 Session 實例。另外一方面,因爲不少地方都須要操做 Session,存在多方法共享 Session 的需求。使用 ThreadLocal 進行實現:

public class SessionHandler {

  public static ThreadLocal<Session> session = ThreadLocal.<Session>withInitial(() -> new Session());

  @Data
  public static class Session {
    private String id;
    private String user;
    private String status;
  }

  public String getUser() {
    return session.get().getUser();
  }

  public String getStatus() {
    return session.get().getStatus();
  }

  public void setStatus(String status) {
    session.get().setStatus(status);
  }

  public static void main(String[] args) {
    new Thread(() -> {
      SessionHandler handler = new SessionHandler();
      handler.getStatus();
      handler.getUser();
      handler.setStatus("close");
      handler.getStatus();
    }).start();
  }
}複製代碼

總結

ThreadLocal 使用起來雖然簡單,但考慮到其設計確實很精巧,值得了解一下。

有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。

death00.github.io/

相關文章
相關標籤/搜索