天天看點

Java多線程--ThreadLocal--原理

簡介

說明

        本文介紹Java的ThreadLocal的原理。

        ThreadLocal是Java多線程常用的技術,也是Java後端面試中常見的問題。

ThreadLocal簡介

        ThreadLocal 的作用是提供線程内的局部變量,這種變量線上程的生命周期内起作用,減少同一個線程内多個函數或者元件之間一些公共變量的傳遞的複雜度。

ThreadLocal用法

要明白原理,先要知道用法。​

本文将最簡用法貼出:

package com.example.a;

public class Demo {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    static void print(String str){
        System.out.println(str + ":" + threadLocal.get());
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("abc");
                print("thread1 variable");
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("def");
                print("thread2 variable");
            }
        });

        thread1.start();
        thread2.start();
    }
}      

運作結果

thread1 variable:abc
thread2 variable:def      

流程追蹤

追蹤上邊簡單用法的流程

set流程

threadLocal.set("abc");         // ThreadLocal#set
  //傳進來的參數名為value

  //擷取目前線程
  Thread t = Thread.currentThread();

  //獲得此線程的ThreadLocalMap變量。(若為null則建立它,若有則直接使用)
  //    ThreadLocalMap是ThreadLocal的靜态内部類。即:ThreadLocal.ThreadLocalMap
  //    ThreadLocalMap内部是弱引用(WeakReference)
  ThreadLocalMap map = getMap(t);   // ThreadLocal#getMap
    return t.threadLocals;

  //儲存資料。key為目前ThreadLocal對象,value為傳進來的值
  map.set(this, value)      

get流程 

threadLocal.get()               // ThreadLocal#get
    //擷取目前線程
    Thread t = Thread.currentThread();

    //獲得此線程的ThreadLocalMap變量
    ThreadLocalMap map = getMap(t);

    //獲得目前線程的以目前ThreadLocal為key的值
    ThreadLocalMap.Entry e = map.getEntry(this);

    //強制轉換為原來的類型并傳回
    T result = (T)e.value;
    return result;      

源碼追蹤 

set源碼

追蹤java.lang.ThreadLocal#getMap

Java多線程--ThreadLocal--原理

它傳回的是t的threadLocals變量,追蹤它

Java多線程--ThreadLocal--原理

可以看到,每個線程(Thread)内部都持有一個ThreadLocal.ThreadLocalMap執行個體

追蹤map.set(this, value)

也就是:java.lang.ThreadLocal.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();
}      

Entry[] tab = table;

tab[i] = new Entry(key, value);

        注意上邊這兩行代碼,可以看到,資料最終是存到了Entry[] table這個數組裡邊。

        追蹤Entry,它的源碼如下:

public class ThreadLocal<T> {
    // 其他代碼
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }
}      

Entry繼承自WeakReference,這個與記憶體洩露有關。後邊會專門寫一篇這個部落格。

key為ThreadLocal對象,value為傳進來的對象。

get源碼

追蹤ThreadLocalMap.Entry e = map.getEntry(this)

java.lang.ThreadLocal.ThreadLocalMap#getEntry

private Entry getEntry(ThreadLocal<?> key) {
  int i = key.threadLocalHashCode & (table.length - 1);
  Entry e = table[i];
  if (e != null && e.get() == key)
    return e;
  else
    return getEntryAfterMiss(key, i, e);
}      

可以看到,它以ThreadLocal對象為key去java.lang.ThreadLocal.ThreadLocalMap#table去查找value。

原理總結(圖文)

Java多線程--ThreadLocal--原理