1. 程式人生 > >【Java8原始碼分析】執行緒-ThreadLocal的全面剖析

【Java8原始碼分析】執行緒-ThreadLocal的全面剖析

一、背景

ThreadLocal類顧名思義就是,申明為ThreadLocal的變數,對於不同執行緒來說都是獨立的。

下面是一個例子:

public class Test {

    public static void main(String[] args) {
        ThreadLocalTest threadLocalTest = new ThreadLocalTest();

        for(int i = 0; i < 3; i++) {
            TaskTest taskTest = new TaskTest(threadLocalTest);
            Thread t = new
Thread(taskTest); t.start(); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } static class TaskTest implements Runnable{ ThreadLocalTest test; public
TaskTest(ThreadLocalTest test) { this.test = test; } @Override public void run() { int tmp = test.localCnt.get(); test.localCnt.set(tmp + 1); test.shareCnt += 1; System.out.println(Thread.currentThread().getName()); System.out.println("LocalCnt:"
+ test.localCnt.get()); System.out.println("SharedCnt:" + test.shareCnt); } } static class ThreadLocalTest { ThreadLocal<Integer> localCnt = new ThreadLocal<Integer>() { public Integer initialValue() { return 0; } }; int shareCnt = 0; public ThreadLocalTest() { } } }

輸出結果:

Thread-0
LocalCnt:1
SharedCnt:1
Thread-1
LocalCnt:1
SharedCnt:2
Thread-2
LocalCnt:1
SharedCnt:3

基本原理:ThreadLocal會為每一個執行緒提供一個獨立的變數副本,從而隔離了多個執行緒對資料的訪問衝突。因為每一個執行緒都擁有自己的變數副本,從而也就沒有必要對該變數進行同步了。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。

二、儲存結構

在ThreadLocal類中定義了一個重要靜態內部類,ThreadLocalMap,用來儲存每個執行緒的區域性變數,程式碼如下

    static class ThreadLocalMap {

        // Entry繼承自WeakReference類,是儲存執行緒私有變數的資料結構
        // ThreadLocal例項作為引用,意味著如果ThreadLocal例項為null
        // 就可以從table中刪除對應的Entry。
        static class Entry extends WeakReference<ThreadLocal<?>> {

            Object value;

            // 把ThreadLocal與value封裝成Entry
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        // 陣列初始大小為16
        private static final int INITIAL_CAPACITY = 16;

        // 儲存陣列
        private Entry[] table;

        private int size = 0;
        private int threshold; // 預設為0
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        // 建構函式
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

        private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

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

            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();
        }
    }

三、主要方法

public class ThreadLocal<T> {

    // 跟hash值相關的部分
    private final int threadLocalHashCode = nextHashCode();
    private static AtomicInteger nextHashCode =
        new AtomicInteger();
    private static final int HASH_INCREMENT = 0x61c88647;
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    // 此方法在每個執行緒中最多執行一次,如果第一次執行get(),會呼叫此方法
    // 如果在第一次執行get()之前已經呼叫過set(),則此方法永遠不執行
    // 可以看到預設返回null值,為了避免不必要錯誤,最好重寫此方法
    protected T initialValue() {
        return null;
    }

    // 建構函式
    public ThreadLocal() {
    }

    // 獲取執行緒所屬的值
    public T get() {

        // 獲取當前執行緒
        Thread t = Thread.currentThread();  

        // 每個執行緒有維護一個ThreadLocalMap變數,呼叫getMap獲取
        ThreadLocalMap map = getMap(t);

        // 如果map不為空
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);

            // 如果map中已經有該ThreadLocal的值,返回
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }

        // 在沒有map或map中沒有新增該ThreadLocal時呼叫初始化
        return setInitialValue();
    }

    // 初始化
    private T setInitialValue() {
        // 呼叫initialValue獲取預設值
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 如果Thread中並沒有map,則新建一個
            // 這裡注意,是每個Thread維護一個ThreadLocalMap
            createMap(t, value);
        return value;
    }

    // 賦值
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 這裡同樣有可能呼叫建立ThreadLocalMap
            createMap(t, value);
    }

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

     // 返回Thread中維護的TreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    // 如果Thread中並沒有map,則新建一個
    // 這裡注意,是每個Thread維護一個ThreadLocalMap
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
}

四、總結

ThreadLocal類最重要的一個概念是,其原理是通過一個ThreadLocal的靜態內部類ThreadLocalMap實現,但是實際中,ThreadLocal不儲存ThreadLocalMap,而是有每個Thread內部維護ThreadLocal.ThreadLocalMap threadLocals一份資料結構。

這裡畫張圖更容易理解,假如我們有如下的程式碼

class ThreadLocalDemo
{
    ThreadLocal<Integer> localA = new ThreadLocal<Integer>();
    ThreadLocal<Integer> localB = new ThreadLocal<Integer>();
}

在多執行緒環境下,資料結構應該是如下圖所示



相關推薦

Java8原始碼分析執行-ThreadLocal全面剖析

一、背景 ThreadLocal類顧名思義就是,申明為ThreadLocal的變數,對於不同執行緒來說都是獨立的。 下面是一個例子: public class Test { public static void main(String[] a

Java8原始碼分析執行-Thread類的全面剖析

一、基本知識 (1)執行緒特性 每個執行緒均有優先順序 執行緒能被標記為守護執行緒 每個執行緒均分配一個name (2)建立執行緒的方法 繼承Thread類,並重現run方法 // 繼承Thread類 class PrimeThread e

Java8原始碼分析併發包-AtomicInteger

AtomicInteger類是實現了原子操作的Integer,以往對於保證int、double、float等基礎型別的運算原子性,需要採用加鎖的方式。但是為了一個簡單的運算操作採用鎖,在多執行緒競爭嚴重的情況下,會導致效能降低,所以在java1.5推出了Atom

Java8原始碼分析NIO包-FileChannel

1 概述 Java NIO 由以下幾個核心部分組成: Buffer Channel Selectors 相關類的使用方法可以參考Java NIO 系列教程,寫的通俗易懂。 本文主要從原始碼方面分析一下Channel類。

Java8原始碼分析NIO包-Selector選擇器

1 概述 Java NIO 由以下幾個核心部分組成: Buffer Channel Selectors 相關類的使用方法可以參考Java NIO 系列教程,寫的通俗易懂。 本文主要從原始碼方面分析一下Selector選擇器。關

Java8原始碼分析併發包-ConcurrentHashMap(一)

一、CAS原理簡介 Java8中,ConcurrentHashMap摒棄了Segment的概念,而是啟用了一種全新的方式實現:利用CAS演算法。它沿用了HashMap的思想,底層依然由“陣列”+連結串列+紅黑樹的方式實現。 那什麼CAS演算法呢?以前採用鎖的

Java8原始碼分析集合框架-HashMap

一、HashMap的儲存結構 總共有兩種儲存類 // 1. 雜湊衝突時採用連結串列法的類,一個雜湊桶多於8個元素改為TreeNode static class Node<K,V> implements Map.Entry<K,V> /

Java8原始碼分析併發包-Semaphore

Semaphore是訊號量,它的作用是控制多個允許。 打個比方,一個博物館有容納1000人的能力(Semaphore的允許是1000),假如1500人同時來參館,只有1000人能獲得允許入館(獲得鎖),其餘的500則需排隊(阻塞),直到館內的人離開(釋

java併發程式設計執行池原理分析及ThreadPoolExecutor原始碼實現

執行緒池簡介:  多執行緒技術主要解決處理器單元內多個執行緒執行的問題,它可以顯著減少處理器單元的閒置時間,增加處理器單元的吞吐能力。         假設一個伺服器完成一項任務所需時間為:T1 建立執行緒時間,T2 線上程中執行任務的時間,T3 銷燬執行緒時間。    

go原始碼分析go原始碼之slice原始碼分析

Go 語言切片是對陣列的抽象。 Go 陣列的長度不可改變,與陣列相比切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大。 len() 和 cap() 函式     切片是可索引的,並且可以由 len() 方法獲取長度。    

go原始碼分析go原始碼之list原始碼分析

本文針對go 1.11版本,路徑src/container/list/list.go 資料結構 Element結構體 Value 前驅 後繼 // Element is an element of a linked list. type Element st

java原始碼分析Map中的hash演算法分析

全網把Map中的hash()分析的最透徹的文章,別無二家。 2018年05月09日 09:08:08 閱讀數:957 你知道HashMap中hash方法的具體實現嗎?你知道HashTable、ConcurrentHashMap中hash方法

Java虛擬機器執行安全與鎖優化

執行緒安全與鎖優化 絕對執行緒安全 相對執行緒安全 執行緒安全的實現方式 互斥同步 非阻塞同步 鎖優化 參考 絕對執行緒安全 當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境

spring原始碼分析IOC容器初始化(二)

前言:在【spring原始碼分析】IOC容器初始化(一)中已經分析了匯入bean階段,本篇接著分析bean解析階段。 1.解析bean程式呼叫鏈 同樣,先給出解析bean的程式呼叫鏈: 根據程式呼叫鏈,整理出在解析bean過程中主要涉及的類和相關方法。 2.解析bean原始碼分

NCNN原始碼分析1.基本資料型別

對於NCNN而言,核心在於網路的前向推理過程(Inference),其主要資料型別為mat,該資料型別以類的形式定義在src/mat.h中,其中包含了mat的建構函式、解構函式、常見的運算過程。 #if

Go 原始碼分析從 sort.go 看排序演算法的工程實踐

go version go1.11 darwin/amd64file: src/sort/sort.go 排序演算法有很多種類,比如快排、堆排、插入排序等。各種排序演算法各有其優劣性,在實際生產過程中用到的排序演算法(或者說 Sort 函式)通常是由幾種排序演算法組

深入原始碼分析Java執行池的實現原理

程式的執行,其本質上,是對系統資源(CPU、記憶體、磁碟、網路等等)的使用。如何高效的使用這些資源是我們程式設計優化演進的一個方向。今天說的執行緒池就是一種對CPU利用的優化手段。 網上有不少介紹如何使用執行緒池的文章,那我想說點什麼呢?我希望通過學習執行緒池原理,明白所有

PHP7原始碼分析如何理解PHP虛擬機器(一)

順風車運營研發團隊 李樂 1.從物理機說起 虛擬機器也是計算機,設計思想和物理機有很多相似之處; 1.1馮諾依曼體系結構 馮·諾依曼是當之無愧的數字計算機之父,當前計算機都採用的是馮諾依曼體系結構;設計思想主要包含以下幾個方面: 指令和資料不加區別混合儲存在同一個儲

Mybatis原始碼分析13-記一次PageHelper reasonable引數使用不當造成的死迴圈

問題描述及原因 使用Mybatis PageHelper外掛做了表的分頁查詢,要求查詢符合某一條件的所有記錄做處理,就寫了一個迭代器在while迴圈裡對每條記錄做處理,直到符合條件的記錄都處理完程式返回。程式碼如下 public class ReconPaymentI

Tomcat9原始碼分析元件與框架概述

1 元件與框架介紹 Server:代表整個Catalina Servlet容器,可以包含一個或多個Service Service:包含Connector和Container的集合,Service用適當的Connector接收使用者的請求,